257 lines
5.1 KiB
Go
257 lines
5.1 KiB
Go
package go_alac
|
|
|
|
import (
|
|
"bytes"
|
|
"github.com/Eyevinn/mp4ff/bits"
|
|
"github.com/Eyevinn/mp4ff/mp4"
|
|
"io"
|
|
"time"
|
|
)
|
|
|
|
type FormatEncoder struct {
|
|
encoder *FrameEncoder
|
|
writer io.Writer
|
|
sampleRate int
|
|
buffer []byte
|
|
outputBuffer [][]byte
|
|
segmentDuration time.Duration
|
|
samplesPerPacket int
|
|
packetsWritten int
|
|
trackId uint32
|
|
seqNumber uint32
|
|
}
|
|
|
|
type AlacBox struct {
|
|
Cookie []byte
|
|
}
|
|
|
|
func (b *AlacBox) Type() string {
|
|
return "alac"
|
|
}
|
|
|
|
// Size - calculated size of box
|
|
func (b *AlacBox) Size() uint64 {
|
|
return uint64(12 + len(b.Cookie))
|
|
}
|
|
|
|
// Encode - write box to w
|
|
func (b *AlacBox) Encode(w io.Writer) error {
|
|
sw := bits.NewFixedSliceWriter(int(b.Size()))
|
|
err := b.EncodeSW(sw)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = w.Write(sw.Bytes())
|
|
return err
|
|
}
|
|
|
|
func (b *AlacBox) EncodeSW(sw bits.SliceWriter) error {
|
|
err := mp4.EncodeHeaderSW(b, sw)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
sw.WriteUint32(0) //version
|
|
sw.WriteBytes(b.Cookie)
|
|
return sw.AccError()
|
|
}
|
|
|
|
func (b *AlacBox) Info(w io.Writer, specificBoxLevels, indent, indentStep string) error {
|
|
return nil
|
|
}
|
|
|
|
func NewFormatEncoder(writer io.Writer, sampleRate, channels, bitDepth int, fastMode bool, segmentDuration time.Duration) *FormatEncoder {
|
|
e := &FormatEncoder{
|
|
encoder: NewFrameEncoder(sampleRate, channels, bitDepth, fastMode),
|
|
writer: writer,
|
|
sampleRate: sampleRate,
|
|
segmentDuration: segmentDuration,
|
|
}
|
|
|
|
if e.encoder == nil {
|
|
return nil
|
|
}
|
|
|
|
e.samplesPerPacket = e.encoder.GetSamplesPerPacket()
|
|
|
|
init := mp4.CreateEmptyInit()
|
|
init.AddEmptyTrack(uint32(sampleRate), "audio", "en")
|
|
e.trackId = init.Moov.Mvhd.NextTrackID - 1
|
|
trak := init.Moov.Trak
|
|
|
|
stsd := trak.Mdia.Minf.Stbl.Stsd
|
|
|
|
//TODO: this does not work with 96kHz freq etc.
|
|
mp4a := mp4.CreateAudioSampleEntryBox("alac", uint16(channels), uint16(bitDepth), uint16(sampleRate), &AlacBox{
|
|
Cookie: e.encoder.GetMagicCookie(),
|
|
})
|
|
stsd.AddChild(mp4a)
|
|
|
|
init.Encode(writer)
|
|
|
|
return e
|
|
}
|
|
|
|
func (e *FormatEncoder) outputPacket(packet []byte) {
|
|
e.outputBuffer = append(e.outputBuffer, packet)
|
|
|
|
if time.Duration(float64(time.Second)*(float64(e.samplesPerPacket*len(e.outputBuffer))/float64(e.sampleRate))) >= e.segmentDuration {
|
|
e.outputSegment()
|
|
}
|
|
|
|
}
|
|
|
|
func (e *FormatEncoder) outputSegment() {
|
|
seg := mp4.NewMediaSegment()
|
|
frag, _ := mp4.CreateFragment(e.seqNumber, e.trackId)
|
|
seg.AddFragment(frag)
|
|
|
|
for _, b := range e.outputBuffer {
|
|
frag.AddFullSampleToTrack(mp4.FullSample{
|
|
Sample: mp4.Sample{
|
|
Dur: uint32(e.samplesPerPacket),
|
|
Size: uint32(len(b)),
|
|
},
|
|
DecodeTime: uint64(e.samplesPerPacket * e.packetsWritten),
|
|
Data: b,
|
|
}, e.trackId)
|
|
e.packetsWritten++
|
|
}
|
|
|
|
seg.Encode(e.writer)
|
|
e.seqNumber++
|
|
e.outputBuffer = nil
|
|
|
|
}
|
|
|
|
func (e *FormatEncoder) Write(pcm []byte) {
|
|
if e.encoder == nil {
|
|
return
|
|
}
|
|
e.buffer = append(e.buffer, pcm...)
|
|
inputSize := e.encoder.GetInputSize()
|
|
for len(e.buffer) >= inputSize {
|
|
e.outputPacket(e.encoder.WritePacket(e.buffer[:inputSize]))
|
|
e.buffer = e.buffer[inputSize:]
|
|
}
|
|
}
|
|
func (e *FormatEncoder) Flush() {
|
|
if e.encoder == nil {
|
|
return
|
|
}
|
|
|
|
if len(e.buffer) > 0 {
|
|
e.outputPacket(e.encoder.WritePacket(e.buffer))
|
|
e.buffer = nil
|
|
}
|
|
|
|
if len(e.outputBuffer) > 0 {
|
|
e.outputSegment()
|
|
}
|
|
}
|
|
|
|
type FormatDecoder struct {
|
|
trex *mp4.TrexBox
|
|
handle *FrameDecoder
|
|
parsedMp4 *mp4.File
|
|
|
|
currentSegment int
|
|
currentFragment int
|
|
}
|
|
|
|
func NewFormatDecoder(reader io.Reader) *FormatDecoder {
|
|
parsedMp4, err := mp4.DecodeFile(reader)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
|
|
var trexEntry *mp4.TrexBox
|
|
var magicCookie []byte
|
|
|
|
//TODO: handle non-segmented
|
|
|
|
for _, trak := range parsedMp4.Moov.Traks {
|
|
if box, err := trak.Mdia.Minf.Stbl.Stsd.GetSampleDescription(0); err == nil && box.Type() == "alac" {
|
|
if parsedMp4.Moov.Mvex == nil {
|
|
continue
|
|
}
|
|
for _, trex := range parsedMp4.Moov.Mvex.Trexs {
|
|
if trex.TrackID == trak.Tkhd.TrackID {
|
|
trexEntry = trex
|
|
break
|
|
}
|
|
}
|
|
|
|
if trexEntry == nil {
|
|
return nil
|
|
}
|
|
|
|
buf := new(bytes.Buffer)
|
|
box.Encode(buf)
|
|
|
|
boxBytes := buf.Bytes()
|
|
|
|
boxOffset := 36 + 12
|
|
|
|
magicCookie = boxBytes[boxOffset:]
|
|
break
|
|
}
|
|
}
|
|
|
|
if trexEntry == nil || magicCookie == nil {
|
|
return nil
|
|
}
|
|
|
|
decoder := NewFrameDecoder(magicCookie)
|
|
if decoder == nil {
|
|
return nil
|
|
}
|
|
|
|
return &FormatDecoder{
|
|
handle: decoder,
|
|
trex: trexEntry,
|
|
parsedMp4: parsedMp4,
|
|
}
|
|
}
|
|
|
|
func (d *FormatDecoder) GetChannels() int {
|
|
return d.handle.GetChannels()
|
|
}
|
|
|
|
func (d *FormatDecoder) GetBitDepth() int {
|
|
return d.handle.GetBitDepth()
|
|
}
|
|
|
|
func (d *FormatDecoder) GetSampleRate() int {
|
|
return d.handle.GetSampleRate()
|
|
}
|
|
|
|
func (d *FormatDecoder) Read() (buf []byte) {
|
|
if d.currentSegment >= len(d.parsedMp4.Segments) {
|
|
//EOF
|
|
return nil
|
|
}
|
|
segment := d.parsedMp4.Segments[d.currentSegment]
|
|
|
|
if d.currentFragment >= len(segment.Fragments) {
|
|
d.currentSegment++
|
|
d.currentFragment = 0
|
|
return d.Read()
|
|
}
|
|
|
|
frag := segment.Fragments[d.currentFragment]
|
|
|
|
samples, err := frag.GetFullSamples(d.trex)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
|
|
for _, sample := range samples {
|
|
_, pcm := d.handle.ReadPacket(sample.Data)
|
|
|
|
buf = append(buf, pcm...)
|
|
}
|
|
d.currentFragment++
|
|
|
|
return
|
|
}
|