This commit is contained in:
parent
6511f2e9c1
commit
709fcf6e33
19
.drone.yml
Normal file
19
.drone.yml
Normal 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
68
decoder.go
Normal 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)
|
||||||
|
}
|
|
@ -29,7 +29,7 @@ func NewFrameEncoder(sampleRate, channels, bitDepth int, fastMode bool) *FrameEn
|
||||||
handle: handle,
|
handle: handle,
|
||||||
}
|
}
|
||||||
|
|
||||||
runtime.SetFinalizer(e, finalize)
|
runtime.SetFinalizer(e, finalizeEncoder)
|
||||||
|
|
||||||
return e
|
return e
|
||||||
}
|
}
|
||||||
|
@ -57,7 +57,7 @@ func (e *FrameEncoder) WritePacket(pcm []byte) []byte {
|
||||||
return output[:outBytes]
|
return output[:outBytes]
|
||||||
}
|
}
|
||||||
|
|
||||||
func finalize(e *FrameEncoder) {
|
func finalizeEncoder(e *FrameEncoder) {
|
||||||
e.Close()
|
e.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
103
mp4.go
103
mp4.go
|
@ -1,6 +1,7 @@
|
||||||
package go_alac
|
package go_alac
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"github.com/edgeware/mp4ff/bits"
|
"github.com/edgeware/mp4ff/bits"
|
||||||
"github.com/edgeware/mp4ff/mp4"
|
"github.com/edgeware/mp4ff/mp4"
|
||||||
"io"
|
"io"
|
||||||
|
@ -100,10 +101,6 @@ func (e *FormatEncoder) outputPacket(packet []byte) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *FormatEncoder) outputSegment() {
|
func (e *FormatEncoder) outputSegment() {
|
||||||
if len(e.outputBuffer) == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
seg := mp4.NewMediaSegment()
|
seg := mp4.NewMediaSegment()
|
||||||
frag, _ := mp4.CreateFragment(e.seqNumber, e.trackId)
|
frag, _ := mp4.CreateFragment(e.seqNumber, e.trackId)
|
||||||
seg.AddFragment(frag)
|
seg.AddFragment(frag)
|
||||||
|
@ -147,5 +144,101 @@ func (e *FormatEncoder) Flush() {
|
||||||
e.buffer = nil
|
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.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.Sync()
|
||||||
o.Close()
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue