Added Decoder, CI
continuous-integration/drone/push Build is passing Details

This commit is contained in:
DataHoarder 2022-04-21 12:49:43 +02:00
parent 6511f2e9c1
commit 709fcf6e33
Signed by: DataHoarder
SSH Key Fingerprint: SHA256:OLTRf6Fl87G52SiR7sWLGNzlJt4WOX+tfI2yxo0z7xk
5 changed files with 221 additions and 8 deletions

19
.drone.yml Normal file
View File

@ -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
...

68
decoder.go Normal file
View File

@ -0,0 +1,68 @@
package go_alac
/*
#cgo pkg-config: alac
#include <alac/libalac.h>
*/
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)
}

View File

@ -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()
}

103
mp4.go
View File

@ -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
}

View File

@ -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()
}