mirror of
https://github.com/qrtc/ffmpeg-dev-go.git
synced 2025-10-04 23:33:15 +08:00
684 lines
24 KiB
Go
684 lines
24 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"syscall"
|
|
"unsafe"
|
|
|
|
ffmpeg "github.com/qrtc/ffmpeg-dev-go"
|
|
)
|
|
|
|
const (
|
|
OUTPUT_BIT_RATE = 96000
|
|
OUTPUT_CHANNELS = 2
|
|
)
|
|
|
|
// Open an input file and the required decoder.
|
|
func openInputFile(fileName string) (inputFormatContext *ffmpeg.AVFormatContext,
|
|
inputCodecContext *ffmpeg.AVCodecContext, ret int32) {
|
|
|
|
// Open the input file to read from it.
|
|
if ret = ffmpeg.AvFormatOpenInput(&inputFormatContext, fileName, nil, nil); ret < 0 {
|
|
fmt.Fprintf(os.Stderr, "Could not open input file '%s' (error '%s')\n", fileName, ffmpeg.AvErr2str(ret))
|
|
return nil, nil, ret
|
|
}
|
|
|
|
// Get information on the input file (number of streams etc.).
|
|
if ret = ffmpeg.AvFormatFindStreamInfo(inputFormatContext, nil); ret < 0 {
|
|
fmt.Fprintf(os.Stderr, "Could not open find stream info (error '%s')\n", ffmpeg.AvErr2str(ret))
|
|
ffmpeg.AvFormatCloseInput(&inputFormatContext)
|
|
return nil, nil, ret
|
|
}
|
|
|
|
// Make sure that there is only one stream in the input file.
|
|
if inputFormatContext.GetNbStreams() != 1 {
|
|
fmt.Fprintf(os.Stderr, "Expected one audio input stream, but found %d\n", inputFormatContext.GetNbStreams())
|
|
ffmpeg.AvFormatCloseInput(&inputFormatContext)
|
|
return nil, nil, ffmpeg.AVERROR_EXIT
|
|
}
|
|
|
|
// Find a decoder for the audio stream.
|
|
inputCodec := ffmpeg.AvCodecFindDecoder(inputFormatContext.GetStreams()[0].GetCodecpar().GetCodecId())
|
|
if inputCodec == nil {
|
|
fmt.Fprintf(os.Stderr, "Could not find input codec\n")
|
|
ffmpeg.AvFormatCloseInput(&inputFormatContext)
|
|
return nil, nil, ffmpeg.AVERROR_EXIT
|
|
}
|
|
|
|
// Allocate a new decoding context.
|
|
avctx := ffmpeg.AvCodecAllocContext3(inputCodec)
|
|
if avctx == nil {
|
|
fmt.Fprintf(os.Stderr, "Could not allocate a decoding context\n")
|
|
ffmpeg.AvFormatCloseInput(&inputFormatContext)
|
|
return nil, nil, ffmpeg.AVERROR(syscall.ENOMEM)
|
|
}
|
|
|
|
// Initialize the stream parameters with demuxer information.
|
|
if ret = ffmpeg.AvCodecParametersToContext(avctx, inputFormatContext.GetStreams()[0].GetCodecpar()); ret < 0 {
|
|
ffmpeg.AvFormatCloseInput(&inputFormatContext)
|
|
ffmpeg.AvCodecFreeContext(&avctx)
|
|
return nil, nil, ret
|
|
}
|
|
|
|
// Open the decoder for the audio stream to use it later.
|
|
if ret = ffmpeg.AvCodecOpen2(avctx, inputCodec, nil); ret < 0 {
|
|
fmt.Fprintf(os.Stderr, "Could not open input codec (error '%s')\n", ffmpeg.AvErr2str(ret))
|
|
ffmpeg.AvCodecFreeContext(&avctx)
|
|
ffmpeg.AvFormatCloseInput(&inputFormatContext)
|
|
return nil, nil, ret
|
|
}
|
|
|
|
// Save the decoder context for easier access later.
|
|
inputCodecContext = avctx
|
|
|
|
return inputFormatContext, inputCodecContext, ret
|
|
}
|
|
|
|
// Open an output file and the required encoder.
|
|
func openOutputFile(filename string, inputCodecContext *ffmpeg.AVCodecContext) (outputFormatContext *ffmpeg.AVFormatContext,
|
|
outputCodecContext *ffmpeg.AVCodecContext, ret int32) {
|
|
|
|
var outputIOContext *ffmpeg.AVIOContext
|
|
var outputCodec *ffmpeg.AVCodec
|
|
var stream *ffmpeg.AVStream
|
|
var avctx *ffmpeg.AVCodecContext
|
|
|
|
// Open the output file to write to it.
|
|
if ret = ffmpeg.AvIOOpen(&outputIOContext, filename, ffmpeg.AVIO_FLAG_WRITE); ret < 0 {
|
|
fmt.Fprintf(os.Stderr, "Could not open output file '%s' (error '%s')\n", filename, ffmpeg.AvErr2str(ret))
|
|
return nil, nil, ret
|
|
}
|
|
|
|
// Create a new format context for the output container format.
|
|
if outputFormatContext = ffmpeg.AvFormatAllocContext(); outputFormatContext == nil {
|
|
fmt.Fprintf(os.Stderr, "Could not allocate output format context\n")
|
|
return nil, nil, ffmpeg.AVERROR(syscall.ENOMEM)
|
|
}
|
|
|
|
// Associate the output file (pointer) with the container format context.
|
|
outputFormatContext.SetPb(outputIOContext)
|
|
|
|
// Guess the desired container format based on the file extension.
|
|
outputFormatContext.SetOformat(ffmpeg.AvGuessFormat(ffmpeg.NIL, filename, ffmpeg.NIL))
|
|
if outputFormatContext.GetOformat() == nil {
|
|
fmt.Fprintf(os.Stderr, "Could not find output file format\n")
|
|
goto cleanup
|
|
}
|
|
|
|
outputFormatContext.SetUrl(filename)
|
|
if len(outputFormatContext.GetUrl()) == 0 {
|
|
fmt.Fprintf(os.Stderr, "Could not allocate url.\n")
|
|
ret = ffmpeg.AVERROR(syscall.ENOMEM)
|
|
goto cleanup
|
|
}
|
|
|
|
// Find the encoder to be used by its name.
|
|
if outputCodec = ffmpeg.AvCodecFindEncoder(ffmpeg.AV_CODEC_ID_AAC); outputCodec == nil {
|
|
fmt.Fprintf(os.Stderr, "Could not find an AAC encoder.\n")
|
|
goto cleanup
|
|
}
|
|
|
|
// Create a new audio stream in the output file container.
|
|
if stream = ffmpeg.AvFormatNewStream(outputFormatContext, nil); stream == nil {
|
|
fmt.Fprintf(os.Stderr, "Could not create new stream\n")
|
|
ret = ffmpeg.AVERROR(syscall.ENOMEM)
|
|
goto cleanup
|
|
}
|
|
|
|
if avctx = ffmpeg.AvCodecAllocContext3(outputCodec); avctx == nil {
|
|
fmt.Fprintf(os.Stderr, "Could not allocate an encoding context\n")
|
|
ret = ffmpeg.AVERROR(syscall.ENOMEM)
|
|
goto cleanup
|
|
}
|
|
|
|
// Set the basic encoder parameters.
|
|
// The input file's sample rate is used to avoid a sample rate conversion.
|
|
avctx.SetChannels(OUTPUT_CHANNELS)
|
|
avctx.SetChannelLayout(uint64(ffmpeg.AvGetDefaultChannelLayout(OUTPUT_CHANNELS)))
|
|
avctx.SetSampleRate(inputCodecContext.GetSampleRate())
|
|
avctx.SetSampleFmt(outputCodec.GetSampleFmts()[0])
|
|
avctx.SetBitRate(OUTPUT_BIT_RATE)
|
|
|
|
// Allow the use of the experimental AAC encoder.
|
|
avctx.SetStrictStdCompliance(ffmpeg.FF_COMPLIANCE_EXPERIMENTAL)
|
|
|
|
// Set the sample rate for the container.
|
|
stream.SetTimeBase(ffmpeg.AvMakeQ(inputCodecContext.GetSampleRate(), 1))
|
|
|
|
// Some container formats (like MP4) require global headers to be present.
|
|
// Mark the encoder so that it behaves accordingly.
|
|
if (outputFormatContext.GetOformat().GetFlags() & ffmpeg.AVFMT_GLOBALHEADER) != 0 {
|
|
avctx.SetFlags(avctx.GetFlags() | ffmpeg.AV_CODEC_FLAG_GLOBAL_HEADER)
|
|
}
|
|
|
|
// Open the encoder for the audio stream to use it later.
|
|
if ret = ffmpeg.AvCodecOpen2(avctx, outputCodec, nil); ret < 0 {
|
|
fmt.Fprintf(os.Stderr, "Could not open output codec (error '%s')\n", ffmpeg.AvErr2str(ret))
|
|
goto cleanup
|
|
}
|
|
|
|
ret = ffmpeg.AvCodecParametersFromContext(stream.GetCodecpar(), avctx)
|
|
if ret < 0 {
|
|
fmt.Fprintf(os.Stderr, "Could not initialize stream parameters\n")
|
|
goto cleanup
|
|
}
|
|
|
|
// Save the encoder context for easier access later.
|
|
outputCodecContext = avctx
|
|
|
|
return outputFormatContext, outputCodecContext, 0
|
|
|
|
cleanup:
|
|
ffmpeg.AvCodecFreeContext(&avctx)
|
|
ffmpeg.AvIOClosep(outputFormatContext.GetPbAddr())
|
|
if ret > 0 {
|
|
ret = ffmpeg.AVERROR_EXIT
|
|
}
|
|
return nil, nil, ret
|
|
}
|
|
|
|
// Initialize one data packet for reading or writing.
|
|
func initPacket() (packet *ffmpeg.AVPacket, ret int32) {
|
|
if packet = ffmpeg.AvPacketAlloc(); packet == nil {
|
|
fmt.Fprintf(os.Stderr, "Could not allocate packet\n")
|
|
return nil, ffmpeg.AVERROR(syscall.ENOMEM)
|
|
}
|
|
return packet, 0
|
|
}
|
|
|
|
// Initialize one audio frame for reading from the input file.
|
|
func initInputFrame() (frame *ffmpeg.AVFrame, ret int32) {
|
|
if frame = ffmpeg.AvFrameAlloc(); frame == nil {
|
|
fmt.Fprintf(os.Stderr, "Could not allocate input frame\n")
|
|
return nil, ffmpeg.AVERROR(syscall.ENOMEM)
|
|
}
|
|
return frame, 0
|
|
}
|
|
|
|
// Initialize the audio resampler based on the input and output codec settings.
|
|
// If the input and output sample formats differ, a conversion is required
|
|
// libswresample takes care of this, but requires initialization.
|
|
func initResampler(inputCodecContext, outputCodecContext *ffmpeg.AVCodecContext) (
|
|
resampleContext *ffmpeg.SwrContext, ret int32) {
|
|
|
|
// Create a resampler context for the conversion.
|
|
// Set the conversion parameters.
|
|
// Default channel layouts based on the number of channels
|
|
// are assumed for simplicity (they are sometimes not detected
|
|
// properly by the demuxer and/or decoder).
|
|
if resampleContext = ffmpeg.SwrAllocSetOpts(nil,
|
|
ffmpeg.AvGetDefaultChannelLayout(outputCodecContext.GetChannels()),
|
|
outputCodecContext.GetSampleFmt(),
|
|
outputCodecContext.GetSampleRate(),
|
|
ffmpeg.AvGetDefaultChannelLayout(inputCodecContext.GetChannels()),
|
|
inputCodecContext.GetSampleFmt(),
|
|
inputCodecContext.GetSampleRate(),
|
|
0, nil); resampleContext == nil {
|
|
fmt.Fprintf(os.Stderr, "Could not allocate resample context\n")
|
|
return nil, ffmpeg.AVERROR(syscall.ENOMEM)
|
|
}
|
|
|
|
if outputCodecContext.GetSampleRate() != inputCodecContext.GetSampleRate() {
|
|
panic("resample has to be handled differently")
|
|
}
|
|
|
|
// Open the resampler with the specified parameters.
|
|
if ret = ffmpeg.SwrInit(resampleContext); ret < 0 {
|
|
fmt.Fprintf(os.Stderr, "Could not open resample context\n")
|
|
ffmpeg.SwrFree(&resampleContext)
|
|
return nil, ret
|
|
}
|
|
return resampleContext, 0
|
|
}
|
|
|
|
// Initialize a FIFO buffer for the audio samples to be encoded.
|
|
func initFifo(outputCodecContext *ffmpeg.AVCodecContext) (fifo *ffmpeg.AVAudioFifo, ret int32) {
|
|
// Create the FIFO buffer based on the specified output sample format
|
|
if fifo = ffmpeg.AvAudioFifoAlloc(outputCodecContext.GetSampleFmt(),
|
|
outputCodecContext.GetChannels(), 1); fifo == nil {
|
|
fmt.Fprintf(os.Stderr, "Could not allocate FIFO\n")
|
|
return nil, ffmpeg.AVERROR(syscall.ENOMEM)
|
|
}
|
|
return fifo, 0
|
|
}
|
|
|
|
// Write the header of the output file container.
|
|
func writeOutputFileHeader(outputFormatContext *ffmpeg.AVFormatContext) int32 {
|
|
if ret := ffmpeg.AvFormatWriteHeader(outputFormatContext, nil); ret < 0 {
|
|
fmt.Fprintf(os.Stderr, "Could not write output file header (error '%s')\n", ffmpeg.AvErr2str(ret))
|
|
return ret
|
|
}
|
|
return 0
|
|
}
|
|
|
|
// Decode one audio frame from the input file.
|
|
func decodeAudioFrame(frame *ffmpeg.AVFrame,
|
|
inputFormatContext *ffmpeg.AVFormatContext,
|
|
inputCodecContext *ffmpeg.AVCodecContext) (dataPresent, finished, ret int32) {
|
|
// Packet used for temporary storage.
|
|
inputPacket, ret := initPacket()
|
|
if ret < 0 {
|
|
return 0, 0, ret
|
|
}
|
|
|
|
// Read one audio frame from the input file into a temporary packet.
|
|
if ret = ffmpeg.AvReadFrame(inputFormatContext, inputPacket); ret < 0 {
|
|
// If we are at the end of the file, flush the decoder below.
|
|
if ret == ffmpeg.AVERROR_EOF {
|
|
finished = 1
|
|
} else {
|
|
fmt.Fprintf(os.Stderr, "Could not read frame (error '%s')\n", ffmpeg.AvErr2str(ret))
|
|
goto cleanup
|
|
}
|
|
}
|
|
|
|
// Send the audio frame stored in the temporary packet to the decoder.
|
|
// The input audio stream decoder is used to do this.
|
|
if ret = ffmpeg.AvCodecSendPacket(inputCodecContext, inputPacket); ret < 0 {
|
|
fmt.Fprintf(os.Stderr, "Could not send packet for decoding (error '%s')\n", ffmpeg.AvErr2str(ret))
|
|
goto cleanup
|
|
}
|
|
|
|
// Receive one frame from the decoder.
|
|
ret = ffmpeg.AvCodecReceiveFrame(inputCodecContext, frame)
|
|
switch {
|
|
// If the decoder asks for more data to be able to decode a frame,
|
|
// return indicating that no data is present.
|
|
case ret == ffmpeg.AVERROR(syscall.EAGAIN):
|
|
ret = 0
|
|
goto cleanup
|
|
// If the end of the input file is reached, stop decoding.
|
|
case ret == ffmpeg.AVERROR_EOF:
|
|
finished = 1
|
|
ret = 0
|
|
goto cleanup
|
|
case ret < 0:
|
|
fmt.Fprintf(os.Stderr, "Could not decode frame (error '%s')\n", ffmpeg.AvErr2str(ret))
|
|
goto cleanup
|
|
// Default case: Return decoded data.
|
|
default:
|
|
dataPresent = 1
|
|
goto cleanup
|
|
}
|
|
|
|
cleanup:
|
|
ffmpeg.AvPacketFree(&inputPacket)
|
|
return dataPresent, finished, ret
|
|
}
|
|
|
|
// Initialize a temporary storage for the specified number of audio samples.
|
|
// The conversion requires temporary storage due to the different format.
|
|
// The number of audio samples to be allocated is specified in frame_size.
|
|
func initConvertedSamples(outputCodecContext *ffmpeg.AVCodecContext,
|
|
frameSize int32) (convertedInputSamples **uint8, ret int32) {
|
|
|
|
// Allocate as many pointers as there are audio channels.
|
|
// Each pointer will later point to the audio samples of the corresponding
|
|
// channels (although it may be NULL for interleaved formats).
|
|
if convertedInputSamples = (**uint8)(ffmpeg.AvCalloc(outputCodecContext.GetChannels(),
|
|
unsafe.Sizeof(*convertedInputSamples))); convertedInputSamples == nil {
|
|
fmt.Fprintf(os.Stderr, "Could not allocate converted input sample pointers\n")
|
|
return nil, ffmpeg.AVERROR(syscall.ENOMEM)
|
|
}
|
|
|
|
// Allocate memory for the samples of all channels in one consecutive
|
|
// block for convenience.
|
|
if ret = ffmpeg.AvSamplesAlloc(convertedInputSamples, nil,
|
|
outputCodecContext.GetChannels(),
|
|
frameSize,
|
|
outputCodecContext.GetSampleFmt(), 0); ret < 0 {
|
|
fmt.Fprintf(os.Stderr, "Could not allocate converted input samples (error '%s')\n", ffmpeg.AvErr2str(ret))
|
|
ffmpeg.AvFreep(&convertedInputSamples)
|
|
return nil, ret
|
|
}
|
|
return convertedInputSamples, 0
|
|
}
|
|
|
|
// Convert the input audio samples into the output sample format.
|
|
// The conversion happens on a per-frame basis, the size of which is
|
|
// specified by frame_size.
|
|
func convertSamples(inputData, convertedData **uint8,
|
|
frameSize int32, resampleContext *ffmpeg.SwrContext) (ret int32) {
|
|
if ret = ffmpeg.SwrConvert(resampleContext, convertedData, frameSize, inputData, frameSize); ret < 0 {
|
|
fmt.Fprintf(os.Stderr, "Could not convert input samples (error '%s')\n", ffmpeg.AvErr2str(ret))
|
|
return ret
|
|
}
|
|
return 0
|
|
}
|
|
|
|
// Add converted input audio samples to the FIFO buffer for later processing.
|
|
func addSamplesToFifo(fifo *ffmpeg.AVAudioFifo, convertedInputSamples **uint8, frameSize int32) int32 {
|
|
// Make the FIFO as large as it needs to be to hold both,
|
|
// the old and the new samples.
|
|
if ret := ffmpeg.AvAudioFifoRealloc(fifo, ffmpeg.AvAudioFifoSize(fifo)+frameSize); ret < 0 {
|
|
fmt.Fprintf(os.Stderr, "Could not reallocate FIFO\n")
|
|
return ret
|
|
}
|
|
|
|
// Store the new samples in the FIFO buffer.
|
|
if ret := ffmpeg.AvAudioFifoWrite(fifo, convertedInputSamples, frameSize); ret < frameSize {
|
|
fmt.Fprintf(os.Stderr, "Could not write data to FIFO\n")
|
|
return ffmpeg.AVERROR_EXIT
|
|
}
|
|
return 0
|
|
}
|
|
|
|
// Read one audio frame from the input file, decode, convert and store it in the FIFO buffer.
|
|
func readDecodeConvertAndStore(fifo *ffmpeg.AVAudioFifo,
|
|
inputFormatContext *ffmpeg.AVFormatContext,
|
|
inputCodecContext *ffmpeg.AVCodecContext,
|
|
outputCodecContext *ffmpeg.AVCodecContext,
|
|
resamplerContext *ffmpeg.SwrContext) (finished, ret int32) {
|
|
|
|
// Temporary storage of the input samples of the frame read from the file.
|
|
var inputFrame *ffmpeg.AVFrame
|
|
// Temporary storage for the converted input samples.
|
|
var convertedInputSamples **uint8
|
|
var dataPresent int32
|
|
ret = ffmpeg.AVERROR_EXIT
|
|
|
|
// Initialize temporary storage for one input frame.
|
|
if inputFrame, ret = initInputFrame(); ret != 0 {
|
|
goto cleanup
|
|
}
|
|
// Decode one frame worth of audio samples.
|
|
if dataPresent, finished, ret = decodeAudioFrame(inputFrame,
|
|
inputFormatContext, inputCodecContext); ret != 0 {
|
|
goto cleanup
|
|
}
|
|
// If we are at the end of the file and there are no more samples
|
|
// in the decoder which are delayed, we are actually finished.
|
|
// This must not be treated as an error.
|
|
if finished != 0 {
|
|
ret = 0
|
|
goto cleanup
|
|
}
|
|
// If there is decoded data, convert and store it.
|
|
if dataPresent != 0 {
|
|
// Initialize the temporary storage for the converted input samples.
|
|
if convertedInputSamples, ret = initConvertedSamples(outputCodecContext,
|
|
inputFrame.GetNbSamples()); ret != 0 {
|
|
goto cleanup
|
|
}
|
|
|
|
// Convert the input samples to the desired output sample format.
|
|
// This requires a temporary storage provided by converted_input_samples.
|
|
if ret = convertSamples(inputFrame.GetExtendedData(),
|
|
convertedInputSamples, inputFrame.GetNbSamples(), resamplerContext); ret != 0 {
|
|
goto cleanup
|
|
}
|
|
|
|
// Add the converted input samples to the FIFO buffer for later processing.
|
|
if ret = addSamplesToFifo(fifo, convertedInputSamples,
|
|
inputFrame.GetNbSamples()); ret != 0 {
|
|
goto cleanup
|
|
}
|
|
ret = 0
|
|
}
|
|
ret = 0
|
|
|
|
cleanup:
|
|
if convertedInputSamples != nil {
|
|
ffmpeg.AvFreep(&convertedInputSamples)
|
|
}
|
|
ffmpeg.AvFrameFree(&inputFrame)
|
|
|
|
return finished, ret
|
|
}
|
|
|
|
// Initialize one input frame for writing to the output file.
|
|
func initOutputFrame(outputcodecContext *ffmpeg.AVCodecContext,
|
|
frameSize int32) (frame *ffmpeg.AVFrame, ret int32) {
|
|
|
|
// Create a new frame to store the audio samples.
|
|
if frame = ffmpeg.AvFrameAlloc(); frame == nil {
|
|
fmt.Fprintf(os.Stderr, "Could not allocate output frame\n")
|
|
return nil, ffmpeg.AVERROR_EXIT
|
|
}
|
|
|
|
// Set the frame's parameters, especially its size and format.
|
|
// av_frame_get_buffer needs this to allocate memory for the
|
|
// audio samples of the frame.
|
|
// Default channel layouts based on the number of channels
|
|
// are assumed for simplicity.
|
|
frame.SetNbSamples(frameSize)
|
|
frame.SetChannelLayout(outputcodecContext.GetChannelLayout())
|
|
frame.SetFormat(outputcodecContext.GetSampleFmt())
|
|
frame.SetSampleRate(outputcodecContext.GetSampleRate())
|
|
|
|
// Allocate the samples of the created frame. This call will make
|
|
// sure that the audio frame can hold as many samples as specified.
|
|
if ret = ffmpeg.AvFrameGetBuffer(frame, 0); ret < 0 {
|
|
fmt.Fprintf(os.Stderr, "Could not allocate output frame samples (error '%s')\n", ffmpeg.AvErr2str(ret))
|
|
ffmpeg.AvFrameFree(&frame)
|
|
return nil, ret
|
|
}
|
|
|
|
return frame, 0
|
|
}
|
|
|
|
// Global timestamp for the audio frames.
|
|
var pts int64
|
|
|
|
// Encode one frame worth of audio to the output file.
|
|
func encodeAudioFrame(frame *ffmpeg.AVFrame,
|
|
outputFormatContext *ffmpeg.AVFormatContext,
|
|
outputCodecContext *ffmpeg.AVCodecContext) (dataPresent, ret int32) {
|
|
// Packet used for temporary storage.
|
|
var outputPacket *ffmpeg.AVPacket
|
|
|
|
if outputPacket, ret = initPacket(); ret < 0 {
|
|
return dataPresent, ret
|
|
}
|
|
|
|
// Set a timestamp based on the sample rate for the container.
|
|
if frame != nil {
|
|
frame.SetPts(pts)
|
|
pts += int64(frame.GetNbSamples())
|
|
}
|
|
|
|
// Send the audio frame stored in the temporary packet to the encoder.
|
|
// The output audio stream encoder is used to do this.
|
|
ret = ffmpeg.AvCodecSendFrame(outputCodecContext, frame)
|
|
if ret == ffmpeg.AVERROR_EOF {
|
|
ret = 0
|
|
goto cleanup
|
|
} else if ret < 0 {
|
|
fmt.Fprintf(os.Stderr, "Could not send packet for encoding (error '%s')\n", ffmpeg.AvErr2str(ret))
|
|
goto cleanup
|
|
}
|
|
|
|
// Receive one encoded frame from the encoder.
|
|
ret = ffmpeg.AvCodecReceivePacket(outputCodecContext, outputPacket)
|
|
// If the encoder asks for more data to be able to provide an
|
|
// encoded frame, return indicating that no data is present.
|
|
if ret == ffmpeg.AVERROR(syscall.EAGAIN) {
|
|
ret = 0
|
|
goto cleanup
|
|
} else if ret == ffmpeg.AVERROR_EOF {
|
|
// If the last frame has been encoded, stop encoding.
|
|
ret = 0
|
|
goto cleanup
|
|
} else if ret < 0 {
|
|
fmt.Fprintf(os.Stderr, "Could not encode frame (error '%s')\n", ffmpeg.AvErr2str(ret))
|
|
goto cleanup
|
|
} else {
|
|
// Default case: Return encoded data.
|
|
dataPresent = 1
|
|
}
|
|
|
|
// Write one audio frame from the temporary packet to the output file.
|
|
if dataPresent != 0 {
|
|
if ret = ffmpeg.AvWriteFrame(outputFormatContext, outputPacket); ret < 0 {
|
|
fmt.Fprintf(os.Stderr, "Could not write frame (error '%s')\n", ffmpeg.AvErr2str(ret))
|
|
goto cleanup
|
|
}
|
|
}
|
|
|
|
cleanup:
|
|
ffmpeg.AvPacketFree(&outputPacket)
|
|
return dataPresent, ret
|
|
}
|
|
|
|
// Load one audio frame from the FIFO buffer, encode and write it to the output file.
|
|
func loadEncodeAndWrite(fifo *ffmpeg.AVAudioFifo,
|
|
outputFormatContext *ffmpeg.AVFormatContext,
|
|
outputCodecContext *ffmpeg.AVCodecContext) (ret int32) {
|
|
// Temporary storage of the output samples of the frame written to the file.
|
|
var outputFrame *ffmpeg.AVFrame
|
|
// Use the maximum number of possible samples per frame.
|
|
// If there is less than the maximum possible frame size in the FIFO
|
|
// buffer use this number. Otherwise, use the maximum possible frame size.
|
|
frameSize := ffmpeg.FFMIN(ffmpeg.AvAudioFifoSize(fifo), outputCodecContext.GetFrameSize())
|
|
|
|
// Initialize temporary storage for one output frame.
|
|
if outputFrame, ret = initOutputFrame(outputCodecContext, frameSize); ret != 0 {
|
|
return ffmpeg.AVERROR_EXIT
|
|
}
|
|
|
|
// Read as many samples from the FIFO buffer as required to fill the frame.
|
|
// The samples are stored in the frame temporarily.
|
|
if ret = ffmpeg.AvAudioFifoRead(fifo, outputFrame.GetData(), frameSize); ret < frameSize {
|
|
fmt.Fprintf(os.Stderr, "Could not read data from FIFO\n")
|
|
ffmpeg.AvFrameFree(&outputFrame)
|
|
return ffmpeg.AVERROR_EXIT
|
|
}
|
|
|
|
// Encode one frame worth of audio samples.
|
|
if _, ret = encodeAudioFrame(outputFrame,
|
|
outputFormatContext, outputCodecContext); ret != 0 {
|
|
ffmpeg.AvFrameFree(&outputFrame)
|
|
return ffmpeg.AVERROR_EXIT
|
|
}
|
|
ffmpeg.AvFrameFree(&outputFrame)
|
|
return 0
|
|
}
|
|
|
|
// Write the trailer of the output file container.
|
|
func writeOutputFileTrailer(outputFormatContext *ffmpeg.AVFormatContext) int32 {
|
|
if ret := ffmpeg.AvWriteTrailer(outputFormatContext); ret < 0 {
|
|
fmt.Fprintf(os.Stderr, "Could not write output file trailer (error '%s')\n", ffmpeg.AvErr2str(ret))
|
|
return ret
|
|
}
|
|
return 0
|
|
}
|
|
|
|
func main() {
|
|
var ret int32 = ffmpeg.AVERROR_EXIT
|
|
var inputFormatContext *ffmpeg.AVFormatContext
|
|
var inputCodecContext *ffmpeg.AVCodecContext
|
|
var outputFormatContext *ffmpeg.AVFormatContext
|
|
var outputCodecContext *ffmpeg.AVCodecContext
|
|
var resampleContext *ffmpeg.SwrContext
|
|
var fifo *ffmpeg.AVAudioFifo
|
|
|
|
if len(os.Args) != 3 {
|
|
fmt.Fprintf(os.Stdout, "Usage: %s <input file> <output file>\n", os.Args[0])
|
|
os.Exit(1)
|
|
}
|
|
|
|
// Open the input file for reading.
|
|
if inputFormatContext, inputCodecContext, ret = openInputFile(os.Args[1]); ret != 0 {
|
|
goto cleanup
|
|
}
|
|
// Open the output file for writing.
|
|
if outputFormatContext, outputCodecContext, ret = openOutputFile(os.Args[2],
|
|
inputCodecContext); ret != 0 {
|
|
goto cleanup
|
|
}
|
|
// Initialize the resampler to be able to convert audio sample formats.
|
|
if resampleContext, ret = initResampler(inputCodecContext, outputCodecContext); ret != 0 {
|
|
goto cleanup
|
|
}
|
|
// Initialize the FIFO buffer to store audio samples to be encoded.
|
|
if fifo, ret = initFifo(outputCodecContext); ret != 0 {
|
|
goto cleanup
|
|
}
|
|
// Write the header of the output file container.
|
|
if ret = writeOutputFileHeader(outputFormatContext); ret != 0 {
|
|
goto cleanup
|
|
}
|
|
|
|
// Loop as long as we have input samples to read or output samples
|
|
// to write; abort as soon as we have neither.
|
|
for {
|
|
// Use the encoder's desired frame size for processing.
|
|
var outputFrameSize int32 = outputCodecContext.GetFrameSize()
|
|
var finished int32
|
|
|
|
// Make sure that there is one frame worth of samples in the FIFO
|
|
// buffer so that the encoder can do its work.
|
|
// Since the decoder's and the encoder's frame size may differ, we
|
|
// need to FIFO buffer to store as many frames worth of input samples
|
|
// that they make up at least one frame worth of output samples.
|
|
for ffmpeg.AvAudioFifoSize(fifo) < outputFrameSize {
|
|
// Decode one frame worth of audio samples, convert it to the
|
|
// output sample format and put it into the FIFO buffer.
|
|
if finished, ret = readDecodeConvertAndStore(fifo, inputFormatContext,
|
|
inputCodecContext, outputCodecContext, resampleContext); ret != 0 {
|
|
goto cleanup
|
|
}
|
|
|
|
// If we are at the end of the input file, we continue
|
|
// encoding the remaining audio samples to the output file.
|
|
if finished != 0 {
|
|
break
|
|
}
|
|
}
|
|
|
|
// If we have enough samples for the encoder, we encode them.
|
|
// At the end of the file, we pass the remaining samples to
|
|
// the encoder.
|
|
for ffmpeg.AvAudioFifoSize(fifo) >= outputFrameSize ||
|
|
(finished != 0 && ffmpeg.AvAudioFifoSize(fifo) > 0) {
|
|
// Take one frame worth of audio samples from the FIFO buffer,
|
|
// encode it and write it to the output file.
|
|
if ret = loadEncodeAndWrite(fifo, outputFormatContext, outputCodecContext); ret != 0 {
|
|
goto cleanup
|
|
}
|
|
}
|
|
|
|
// If we are at the end of the input file and have encoded
|
|
// all remaining samples, we can exit this loop and finish.
|
|
if finished != 0 {
|
|
var dataWritten int32
|
|
// Flush the encoder as it may have delayed frames.
|
|
for ok := true; ok; ok = dataWritten != 0 {
|
|
if dataWritten, ret = encodeAudioFrame(nil,
|
|
outputFormatContext, outputCodecContext); ret != 0 {
|
|
goto cleanup
|
|
}
|
|
}
|
|
break
|
|
}
|
|
}
|
|
|
|
// Write the trailer of the output file container.
|
|
if ret = writeOutputFileTrailer(outputFormatContext); ret != 0 {
|
|
goto cleanup
|
|
}
|
|
ret = 0
|
|
|
|
cleanup:
|
|
if fifo != nil {
|
|
ffmpeg.AvAudioFifoFree(fifo)
|
|
}
|
|
ffmpeg.SwrFree(&resampleContext)
|
|
if outputCodecContext != nil {
|
|
ffmpeg.AvCodecFreeContext(&outputCodecContext)
|
|
}
|
|
if outputFormatContext != nil {
|
|
ffmpeg.AvIOClosep(outputFormatContext.GetPbAddr())
|
|
ffmpeg.AvFormatFreeContext(outputFormatContext)
|
|
}
|
|
if inputCodecContext != nil {
|
|
ffmpeg.AvCodecFreeContext(&inputCodecContext)
|
|
}
|
|
if inputFormatContext != nil {
|
|
ffmpeg.AvFormatCloseInput(&inputFormatContext)
|
|
}
|
|
|
|
os.Exit(int(ret))
|
|
}
|