Added Decoder, CI
continuous-integration/drone/push Build is passing
Details
continuous-integration/drone/push Build is passing
Details
This commit is contained in:
parent
6511f2e9c1
commit
709fcf6e33
|
@ -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
|
||||
|
||||
...
|
|
@ -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)
|
||||
}
|
|
@ -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
103
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
|
||||
}
|
||||
|
|
35
mp4_test.go
35
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()
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue