//go:build !disable_format_alac && enable_codec_libalac // +build !disable_format_alac,enable_codec_libalac package alac import ( "errors" "git.gammaspectra.live/S.O.N.G/Kirika/audio" "git.gammaspectra.live/S.O.N.G/Kirika/audio/format" "git.gammaspectra.live/S.O.N.G/Kirika/cgo" go_alac "git.gammaspectra.live/S.O.N.G/go-alac" "io" "time" "unsafe" ) type Format struct { } func NewFormat() Format { return Format{} } func (f Format) Name() string { return "alac" } func (f Format) Description() string { return "libalac (S.O.N.G/go-alac)" } func (f Format) Open(r io.ReadSeekCloser) (audio.Source, error) { mp4Demuxer, err := tryDecodeMP4(r) if err != nil { return audio.Source{}, err } decoder := go_alac.NewFrameDecoder(mp4Demuxer.cookie) if decoder == nil { return audio.Source{}, errors.New("could not decode") } newChannel := make(chan []float32) go func() { defer close(newChannel) for { samples := mp4Demuxer.Read() if samples == nil { return } for _, sample := range samples { _, pcm := decoder.ReadPacket(sample) if pcm == nil { return } newChannel <- cgo.Int32ToFloat32(cgo.BytesToInt32(pcm, decoder.GetBitDepth()), decoder.GetBitDepth()) } } }() return audio.Source{ Channels: decoder.GetChannels(), SampleRate: decoder.GetSampleRate(), Blocks: newChannel, }, nil } func (f Format) OpenAnalyzer(r io.ReadSeekCloser) (audio.Source, format.AnalyzerChannel, error) { mp4Demuxer, err := tryDecodeMP4(r) if err != nil { return audio.Source{}, nil, err } decoder := go_alac.NewFrameDecoder(mp4Demuxer.cookie) if decoder == nil { return audio.Source{}, nil, errors.New("could not decode") } newChannel := make(chan []float32) analyzerChannel := make(format.AnalyzerChannel) go func() { defer close(newChannel) defer close(analyzerChannel) for { samples := mp4Demuxer.Read() if samples == nil { return } for _, sample := range samples { _, pcm := decoder.ReadPacket(sample) if pcm == nil { return } intSamples := cgo.BytesToInt32(pcm, decoder.GetBitDepth()) newChannel <- cgo.Int32ToFloat32(intSamples, decoder.GetBitDepth()) analyzerChannel <- &format.AnalyzerPacket{ Samples: intSamples, Channels: decoder.GetChannels(), BitDepth: decoder.GetBitDepth(), SampleRate: decoder.GetSampleRate(), } } } }() return audio.Source{ Channels: decoder.GetChannels(), SampleRate: decoder.GetSampleRate(), Blocks: newChannel, }, analyzerChannel, nil } func (f Format) Encode(source audio.Source, writer io.WriteCloser, options map[string]interface{}) error { var bitsPerSample = 16 var fastMode = false var segmentDuration = time.Millisecond * 100 if options != nil { var val interface{} var ok bool var intVal int var int64Val int64 var boolVal bool if val, ok = options["bitdepth"]; ok { if intVal, ok = val.(int); ok { bitsPerSample = intVal } else if int64Val, ok = val.(int64); ok { bitsPerSample = int(int64Val) } } if val, ok = options["fast"]; ok { if boolVal, ok = val.(bool); ok { fastMode = boolVal } } } encoder := go_alac.NewFormatEncoder(writer, source.SampleRate, source.Channels, bitsPerSample, fastMode, segmentDuration) if encoder == nil { return errors.New("could not create encoder") } defer encoder.Flush() switch bitsPerSample { case 32: for block := range source.Blocks { samples := cgo.Float32ToInt32(block, 32) encoder.Write(unsafe.Slice((*byte)(unsafe.Pointer(&samples[0])), len(samples)*4)) } case 24: for block := range source.Blocks { samples := cgo.Float32ToInt24(block) encoder.Write(unsafe.Slice((*byte)(unsafe.Pointer(&samples[0])), len(samples))) } case 16: for block := range source.Blocks { samples := cgo.Float32ToInt16(block) encoder.Write(unsafe.Slice((*byte)(unsafe.Pointer(&samples[0])), len(samples)*2)) } case 8: for block := range source.Blocks { samples := cgo.Float32ToInt8(block) encoder.Write(unsafe.Slice((*byte)(unsafe.Pointer(&samples[0])), len(samples))) } default: return errors.New("not supported bits per sample") } return nil } func (f Format) Identify(peek []byte, extension string) bool { return extension == "alac" || extension == "mp4" || extension == "m4a" }