From 709fcf6e33e8991516ce7e9376be3087326f450f Mon Sep 17 00:00:00 2001 From: WeebDataHoarder <57538841+WeebDataHoarder@users.noreply.github.com> Date: Thu, 21 Apr 2022 12:49:43 +0200 Subject: [PATCH] Added Decoder, CI --- .drone.yml | 19 ++++++++++ decoder.go | 68 ++++++++++++++++++++++++++++++++++ encoder.go | 4 +- mp4.go | 103 +++++++++++++++++++++++++++++++++++++++++++++++++--- mp4_test.go | 35 +++++++++++++++++- 5 files changed, 221 insertions(+), 8 deletions(-) create mode 100644 .drone.yml create mode 100644 decoder.go diff --git a/.drone.yml b/.drone.yml new file mode 100644 index 0000000..89289a0 --- /dev/null +++ b/.drone.yml @@ -0,0 +1,19 @@ +--- +kind: pipeline +type: docker +name: default + +steps: + - name: test + image: golang:1.18-bullseye + commands: + - DEBIAN_FRONTEND=noninteractive apt update + - DEBIAN_FRONTEND=noninteractive apt install -y git build-essential autoconf automake make libtool-bin ffmpeg + - git clone --depth 1 https://git.gammaspectra.live/S.O.N.G/alac.git && cd alac && autoreconf -fi && ./configure --prefix /usr && make && make install && cd .. + - cd sample + - ffmpeg -i MooveKa_-_Rockabilly_Punk_Rock.mp3 -map 0:a:0 -ar 44100 -ac 2 -c:a pcm_s16le -f s16le test_s16.raw + - ffmpeg -i MooveKa_-_Rockabilly_Punk_Rock.mp3 -map 0:a:0 -ar 44100 -ac 2 -c:a alac -frag_duration 1000 -min_frag_duration 100 test_s16_input.m4a + - cd .. + - go test -cover -v + +... \ No newline at end of file diff --git a/decoder.go b/decoder.go new file mode 100644 index 0000000..0dad148 --- /dev/null +++ b/decoder.go @@ -0,0 +1,68 @@ +package go_alac + +/* +#cgo pkg-config: alac +#include +*/ +import "C" +import ( + "runtime" + "unsafe" +) + +type FrameDecoder struct { + handle C.alac_decoder +} + +func NewFrameDecoder(magicCookie []byte) *FrameDecoder { + handle := C.alac_decoder_new((*C.uchar)(unsafe.Pointer(&magicCookie[0])), C.int(len(magicCookie))) + + if handle.ptr == nil { + return nil + } + + e := &FrameDecoder{ + handle: handle, + } + + runtime.SetFinalizer(e, finalizeDecoder) + + return e +} + +func (e *FrameDecoder) GetInputSize() int { + return int(e.handle.input_max_packet_size) +} + +func (e *FrameDecoder) GetSamplesPerPacket() int { + return int(e.handle.frames_per_packet) +} + +func (e *FrameDecoder) GetChannels() int { + return int(e.handle.channels) +} + +func (e *FrameDecoder) GetBitDepth() int { + return int(e.handle.bit_depth) +} + +func (e *FrameDecoder) GetSampleRate() int { + return int(e.handle.sample_rate) +} + +func (e *FrameDecoder) ReadPacket(data []byte) (inputBytesUsed int, pcm []byte) { + output := make([]byte, int(e.handle.output_packet_size)) + outStruct := C.alac_decoder_read(&e.handle, (*C.uchar)(unsafe.Pointer(&data[0])), C.int(len(data)), (*C.uchar)(unsafe.Pointer(&output[0]))) + if outStruct.input_bytes_used < 0 { + return 0, nil + } + return int(outStruct.input_bytes_used), output[:outStruct.output_bytes] +} + +func finalizeDecoder(e *FrameDecoder) { + e.Close() +} + +func (e *FrameDecoder) Close() { + C.alac_decoder_delete(&e.handle) +} diff --git a/encoder.go b/encoder.go index 22ccddf..ac409f0 100644 --- a/encoder.go +++ b/encoder.go @@ -29,7 +29,7 @@ func NewFrameEncoder(sampleRate, channels, bitDepth int, fastMode bool) *FrameEn handle: handle, } - runtime.SetFinalizer(e, finalize) + runtime.SetFinalizer(e, finalizeEncoder) return e } @@ -57,7 +57,7 @@ func (e *FrameEncoder) WritePacket(pcm []byte) []byte { return output[:outBytes] } -func finalize(e *FrameEncoder) { +func finalizeEncoder(e *FrameEncoder) { e.Close() } diff --git a/mp4.go b/mp4.go index e287132..9fd3a90 100644 --- a/mp4.go +++ b/mp4.go @@ -1,6 +1,7 @@ package go_alac import ( + "bytes" "github.com/edgeware/mp4ff/bits" "github.com/edgeware/mp4ff/mp4" "io" @@ -100,10 +101,6 @@ func (e *FormatEncoder) outputPacket(packet []byte) { } func (e *FormatEncoder) outputSegment() { - if len(e.outputBuffer) == 0 { - return - } - seg := mp4.NewMediaSegment() frag, _ := mp4.CreateFragment(e.seqNumber, e.trackId) seg.AddFragment(frag) @@ -147,5 +144,101 @@ func (e *FormatEncoder) Flush() { e.buffer = nil } - //TODO: flush end + 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) 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 } diff --git a/mp4_test.go b/mp4_test.go index 1cd3e01..d00a697 100644 --- a/mp4_test.go +++ b/mp4_test.go @@ -42,6 +42,39 @@ func TestEncodeMP4(t *testing.T) { encoder.Write(data) } + encoder.Flush() + + o.Sync() +} + +func TestDecodeMP4(t *testing.T) { + t.Parallel() + + f, err := os.Open("sample/test_s16_input.m4a") + if err != nil { + t.Error(err) + return + } + defer f.Close() + + o, err := os.Create("sample/test_s16_output.raw") + if err != nil { + t.Error(err) + return + } + defer o.Close() + + decoder := NewFormatDecoder(f) + if decoder == nil { + t.Fail() + return + } + for { + data := decoder.Read() + if data == nil { + break + } + o.Write(data) + } o.Sync() - o.Close() }