Hi, a very nice example, thanks a lot for submitting it. Some minor comments follow.
On Wed, 02 Oct 2013 18:14:15 +0200, Andreas Unterweger <[email protected]> wrote: > From 8d090d20f9a9bf0286d55864f5b7c7fd84b337d7 Mon Sep 17 00:00:00 2001 > From: Andreas Unterweger <[email protected]> > Date: Wed, 2 Oct 2013 18:08:51 +0200 > Subject: [PATCH] Added an Libav API usage example > > --- > doc/example/Makefile | 15 ++ > doc/example/mp3_aac.c | 692 > +++++++++++++++++++++++++++++++++++++++++++++++++ > 2 files changed, 707 insertions(+) > create mode 100644 doc/example/Makefile > create mode 100644 doc/example/mp3_aac.c > > diff --git a/doc/example/Makefile b/doc/example/Makefile > new file mode 100644 > index 0000000..ddc53bb > --- /dev/null > +++ b/doc/example/Makefile > @@ -0,0 +1,15 @@ > +CC=gcc > + > +CFLAGS=-c -Wall > +LDFLAGS=-lavformat -lavcodec -lavresample -lavutil -lvo-aacenc -lpthread -lm > + > +all: mp3_to_aac > + > +mp3_to_aac: mp3_aac > + $(CC) mp3_aac.o -o mp3_to_aac.exe $(LDFLAGS) > + > +mp3_aac: mp3_aac.c > + $(CC) $(CFLAGS) mp3_aac.c > + > +clean: > + rm -rf *.o *.exe > diff --git a/doc/example/mp3_aac.c b/doc/example/mp3_aac.c > new file mode 100644 > index 0000000..242b95f > --- /dev/null > +++ b/doc/example/mp3_aac.c > @@ -0,0 +1,692 @@ > +/* > + * This file is part of Libav. > + * > + * Libav is free software; you can redistribute it and/or > + * modify it under the terms of the GNU Lesser General Public > + * License as published by the Free Software Foundation; either > + * version 2.1 of the License, or (at your option) any later version. > + * > + * Libav is distributed in the hope that it will be useful, > + * but WITHOUT ANY WARRANTY; without even the implied warranty of > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU > + * Lesser General Public License for more details. > + * > + * You should have received a copy of the GNU Lesser General Public > + * License along with Libav; if not, write to the Free Software > + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 > USA > + */ > + > +/** > + * @file MP3 to AAC converter > + * Convert an MP3 file to an AAC in an MP4 container using Libav. > + * Requires libvo_aacenc to compile. > + * Use -lavformat -lavcodec -lavresample -lavutil -lvo-aacenc -lpthread -lm > + * @author Andreas Unterweger > + */ > + > +#include <stdio.h> > +#include <libavformat/avformat.h> > +#include <libavformat/avio.h> > +#include <libavcodec/avcodec.h> > +#include <libavutil/audio_fifo.h> > +#include <libavutil/opt.h> > +#include <libavresample/avresample.h> > + > +/** The input file path */ > +#define INPUT_FILENAME "test.mp3" > +/** The output file path */ > +#define OUTPUT_FILENAME "test.mp4" > +/** The output codec name (AAC via libvo_aacenc) */ > +#define OUTPUT_CODEC_NAME "libvo_aacenc" > +/** The output bit rate in kbit/s */ > +#define OUTPUT_BIT_RATE 48000 > +/** The number of output channels */ > +#define OUTPUT_CHANNELS 2 > +/** The audio sample output format */ > +#define OUTPUT_SAMPLE_FORMAT AV_SAMPLE_FMT_S16 > + > +/** > + * Convert an error code into a text message. > + * @param error Error code to be converted > + * @return Corresponding error text (not thread-safe) > + */ > +static char *const get_error_text(const int error) > +{ > + static char error_buffer[255]; > + av_strerror(error, error_buffer, sizeof(error_buffer)); > + return error_buffer; > +} > + > +/** Open an input file and the required decoder. */ > +static int open_input_file(const char *filename, > + AVFormatContext **input_format_context, > + AVCodecContext **input_codec_context) > +{ > + AVCodec *input_codec; > + int error; > + /** Open the input file to read from it. */ > + if ((error = avformat_open_input(input_format_context, filename, NULL, > NULL)) < 0) { > + fprintf(stderr, "Could not open input file '%s' (error '%s')\n", > + filename, get_error_text(error)); > + return error; I know input_format_context is set to NULL where it's declared, but perhaps it'd be more clear to the reader if it was explicitly NULLed here. > + } > + /** Get information on the input file (number of streams etc.). */ > + if ((error = avformat_find_stream_info(*input_format_context, NULL)) < > 0) { > + fprintf(stderr, "Could not open find stream info (error '%s')\n", > + get_error_text(error)); > + return error; > + } > + /** Make sure that there is only one stream in the input file. */ > + if ((*input_format_context)->nb_streams != 1) { > + fprintf(stderr, "Expected one audio input stream, but found %d\n", > + (*input_format_context)->nb_streams); > + avformat_close_input(input_format_context); > + return AVERROR_EXIT; > + } > + /** Find a decoder for the audio stream. */ > + if (!(input_codec = > avcodec_find_decoder((*input_format_context)->streams[0]->codec->codec_id))) { > + fprintf(stderr, "Could not find input codec\n"); > + avformat_close_input(input_format_context); > + return AVERROR_EXIT; > + } > + /** Open the decoder for the audio stream to use it later. */ > + if ((error = avcodec_open2((*input_format_context)->streams[0]->codec, > input_codec, NULL)) < 0) { > + fprintf(stderr, "Could not open input codec (error '%s')\n", > get_error_text(error)); > + avformat_close_input(input_format_context); > + return error; > + } > + /** Save the decoder context for easier access later. */ > + *input_codec_context = (*input_format_context)->streams[0]->codec; > + return 0; > +} > + > +/** > + * Open an output file and the required encoder. > + * Also set some basic encoder parameters. > + * Some of these parameters are based on the input file's parameters. > + */ > +static int open_output_file(const char *filename, > + AVCodecContext *input_codec_context, > + AVFormatContext **output_format_context, > + AVCodecContext **output_codec_context) > +{ > + AVIOContext *output_io_context = NULL; > + AVStream *stream = NULL; > + AVCodec *output_codec = NULL; > + int error; > + /** Open the output file to write to it. */ > + if ((error = avio_open(&output_io_context, filename, AVIO_FLAG_WRITE)) < > 0) { > + fprintf(stderr, "Could not open output file '%s' (error '%s')\n", > + filename, get_error_text(error)); > + return error; > + } > + /** Create a new format context for the output container format. */ > + if (!(*output_format_context = avformat_alloc_context())) { > + fprintf(stderr, "Could not allocate output format context\n"); > + return AVERROR(ENOMEM); > + } > + /** Associate the output file (pointer) with the container format > context. */ > + (*output_format_context)->pb = output_io_context; > + /** Guess the desired container format based on the file extension. */ > + if (!((*output_format_context)->oformat = av_guess_format(NULL, > filename, NULL))) { > + fprintf(stderr, "Could not find output file format\n"); > + avformat_close_input(output_format_context); > + return AVERROR_EXIT; > + } > + /** Find the encoder to be used by its name. */ > + if (!(output_codec = avcodec_find_encoder_by_name(OUTPUT_CODEC_NAME))) { > + fprintf(stderr, "Could not find output codec '%s'\n", > OUTPUT_CODEC_NAME); > + avformat_close_input(output_format_context); > + return AVERROR_EXIT; > + } > + /** Create a new audio stream in the output file container. */ > + if (!(stream = avformat_new_stream(*output_format_context, > output_codec))) { > + fprintf(stderr, "Could not create new stream\n"); > + avformat_close_input(output_format_context); > + return AVERROR(ENOMEM); > + } > + /** Save the encoder context for easiert access later. */ > + *output_codec_context = stream->codec; > + /** > + * Set the basic encoder parameters. > + * The input file's sample rate is used to avoid a sample rate > conversion. > + */ > + (*output_codec_context)->channels = OUTPUT_CHANNELS; > + (*output_codec_context)->sample_rate = input_codec_context->sample_rate; > + (*output_codec_context)->sample_fmt = AV_SAMPLE_FMT_S16; > + (*output_codec_context)->bit_rate = OUTPUT_BIT_RATE; > + /** > + * Some container formats (like MP4) require global headers to be present > + * Mark the encoder so that it behaves accordingly. > + */ > + if ((*output_format_context)->oformat->flags & AVFMT_GLOBALHEADER) > + (*output_codec_context)->flags |= CODEC_FLAG_GLOBAL_HEADER; > + /** Open the encoder for the audio stream to use it later. */ > + if ((error = avcodec_open2(*output_codec_context, output_codec, NULL)) < > 0) { > + fprintf(stderr, "Could not open output codec (error '%s')\n", > get_error_text(error)); > + avformat_close_input(output_format_context); > + return error; > + } > + return 0; > +} > + > +/** Initialize one data packet for reading or writing. */ > +static void init_packet(AVPacket *packet) > +{ > + av_init_packet(packet); > + /** Set the packet data and size so that it is recognized as being > empty. */ > + packet->data = NULL; > + packet->size = 0; > +} > + > +/** Initialize one audio frame for reading from the input file */ > +static int init_input_frame(AVFrame **frame) > +{ > + if (!(*frame = avcodec_alloc_frame())) { > + fprintf(stderr, "Could not allocate input frame\n"); > + return AVERROR(ENOMEM); > + } > + avcodec_get_frame_defaults(*frame); avcodec_alloc_frame() already calls avcodec_get_frame_defaults(), so there is no need for this. > + return 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 > + * libavresample takes care of this, but requires initialization. > + */ > +static int init_resampler(AVCodecContext *input_codec_context, > + AVCodecContext *output_codec_context, > + AVAudioResampleContext **resample_context) > +{ > + /** > + * Only initialize the resampler if it is necessary, i.e., > + * if and only if the sample formats differ. > + */ > + if (input_codec_context->sample_fmt != output_codec_context->sample_fmt > || > + input_codec_context->channels != output_codec_context->channels) { > + int error; > + /** Create a resampler context for the conversion. */ > + if (!(*resample_context = avresample_alloc_context())) { > + fprintf(stderr, "Could not allocate resample context\n"); > + return AVERROR(ENOMEM); > + } > + /** > + * 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). > + */ > + av_opt_set_int(*resample_context, "in_channel_layout", > + av_get_default_channel_layout(input_codec_context->channels), 0); > + av_opt_set_int(*resample_context, "out_channel_layout", > + av_get_default_channel_layout(output_codec_context->channels), > 0); > + av_opt_set_int(*resample_context, "in_sample_rate", > + input_codec_context->sample_rate, 0); > + av_opt_set_int(*resample_context, "out_sample_rate", > + output_codec_context->sample_rate, 0); > + av_opt_set_int(*resample_context, "in_sample_fmt", > + input_codec_context->sample_fmt, 0); > + av_opt_set_int(*resample_context, "out_sample_fmt", > + output_codec_context->sample_fmt, 0); > + /** Open the resampler with the specified parameters. */ > + if ((error = avresample_open(*resample_context)) < 0) { > + fprintf(stderr, "Could not open resample context\n"); > + avresample_free(resample_context); > + return error; > + } > + } > + return 0; > +} > + > +/** Initialize a FIFO buffer for the audio samples to be encoded. */ > +static int init_fifo(AVAudioFifo **fifo) > +{ > + /** Create the FIFO buffer based on the specified output sample format. > */ > + if (!(*fifo = av_audio_fifo_alloc(OUTPUT_SAMPLE_FORMAT, OUTPUT_CHANNELS, > 1))) { > + fprintf(stderr, "Could not allocate FIFO\n"); > + return AVERROR(ENOMEM); > + } > + return 0; > +} > + > +/** Write the header of the output file container. */ > +static int write_output_file_header(AVFormatContext *output_format_context) > +{ > + int error; > + if ((error = avformat_write_header(output_format_context, NULL)) < 0) { > + fprintf(stderr, "Could not write output file header (error '%s')\n", > get_error_text(error)); > + return error; > + } > + return 0; > +} > + > +/** Deallocate one packet. */ > +static void uninit_packet(AVPacket *packet) > +{ > + av_free_packet(packet); > +} Why not call av_free_packet directly? > + > +/** Decode one audio frame from the input file. */ > +static int decode_audio_frame(AVFrame *frame, > + AVFormatContext *input_format_context, > + AVCodecContext *input_codec_context, > + int *data_present) > +{ > + /** Packet used for temporary storage. */ > + AVPacket input_packet; > + int error; > + init_packet(&input_packet); > + /** Read one audio frame from the input file into a temporary packet. */ > + if ((error = av_read_frame(input_format_context, &input_packet)) < 0) { > + /** Abort on end of file, but do not treat it as an actual error. */ > + if (error == AVERROR_EOF) { > + *data_present = 0; > + uninit_packet(&input_packet); > + return 0; > + } > + fprintf(stderr, "Could not read frame (error '%s')\n", > get_error_text(error)); > + return error; > + } > + /** > + * Decode the audio frame stored in the temporary packet. > + * The input audio stream decoder is used to do this. > + */ > + if ((error = avcodec_decode_audio4(input_codec_context, frame, > data_present, &input_packet)) < 0) { > + fprintf(stderr, "Could not decode frame (error '%s')\n", > get_error_text(error)); > + uninit_packet(&input_packet); > + return error; > + } > + uninit_packet(&input_packet); > + return 0; > +} > + > +/** > + * 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. > + */ > +static int init_converted_samples(uint8_t ***converted_input_samples, > + AVCodecContext *output_codec_context, > + int frame_size) > +{ > + int error; > + /** > + * 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 (!(*converted_input_samples = calloc(output_codec_context->channels, > sizeof(**converted_input_samples)))) { > + fprintf(stderr, "Could not allocate converted input sample > pointers\n"); > + return AVERROR(ENOMEM); > + } > + /** > + * Allocate memory for the samples of all channels in one consecutive > + * block for convenience. > + */ > + if ((error = av_samples_alloc(*converted_input_samples, NULL, > output_codec_context->channels, > + frame_size, > output_codec_context->sample_fmt, 0)) < 0) { > + fprintf(stderr, "Could not allocate converted input samples (error > '%s')\n", get_error_text(error)); > + av_freep(&converted_input_samples[0]); > + free(converted_input_samples); > + return error; > + } > + return 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. > + */ > +static int convert_samples(uint8_t **input_data, > + uint8_t **converted_data, const int frame_size, > + AVAudioResampleContext *resample_context) > +{ > + int error; > + /** Convert the samples using the resampler. */ > + if ((error = avresample_convert(resample_context, converted_data, 0, > + frame_size, input_data, 0, frame_size)) > < 0) { > + fprintf(stderr, "Could not convert input samples (error '%s')\n", > + get_error_text(error)); > + return error; > + } > + /** > + * Perform a sanity check so that the number of converted samples is > + * not greater than the number of samples to be converted. > + * If the sample rates differ, this case has to be handled differently > + */ > + if (avresample_available(resample_context)) { > + fprintf(stderr, "Converted samples left over\n"); > + return AVERROR_EXIT; > + } > + return 0; > +} > + > +/** Add converted input audio samples to the FIFO buffer for later > processing. */ > +static int add_samples_to_fifo(AVAudioFifo *fifo, > + uint8_t **converted_input_samples, > + const int frame_size) > +{ > + int error; > + /** > + * Make the FIFO as large as it needs to be to hold both, > + * the old and the new samples. > + */ > + if ((error = av_audio_fifo_realloc(fifo, av_audio_fifo_size(fifo) + > frame_size)) < 0) { > + fprintf(stderr, "Could not reallocate FIFO\n"); > + return error; > + } > + /** Store the new samples in the FIFO buffer. */ > + if (av_audio_fifo_write(fifo, (void **)converted_input_samples, > frame_size) < frame_size) { > + fprintf(stderr, "Could not write data to FIFO\n"); > + return AVERROR_EXIT; > + } > + return 0; > +} You could simplify the code a bit by using avresample itself as your FIFO. Though OTOH maybe it's better to show the use of the AVAudioFifo API, not sure... > + > +/** Deallocate one frame used for reading from the input file. */ > +static void uninit_input_frame(AVFrame *frame) > +{ > + avcodec_free_frame(&frame); > +} > + > +/** > + * Read one audio frame from the input file, decodes, converts and stores > + * it in the FIFO buffer. > + */ > +static int read_decode_convert_and_store(AVAudioFifo *fifo, AVFormatContext > *input_format_context, > + AVCodecContext *input_codec_context, > + AVCodecContext > *output_codec_context, > + AVAudioResampleContext > *resampler_context, int *finished) > +{ > + /** Temporary storage of the input samples of the frame read from the > file. */ > + AVFrame *input_frame = NULL; > + /** Temporary storage for the converted input samples. */ > + uint8_t **converted_input_samples = NULL; > + int input_frame_size; > + int data_present; > + int ret = AVERROR_EXIT; > + > + /** Initialize temporary storage for one input frame. */ > + if (init_input_frame(&input_frame)) > + goto cleanup; > + /** Decode one frame worth of audio samples. */ > + if (decode_audio_frame(input_frame, input_format_context, > input_codec_context, &data_present)) > + goto cleanup; > + /** > + * If there is no more data, we are at the end of the file. > + * Although this may have other reasons as well, an error > + * would have printed an error message above and this code > + * path could not have been reached. > + */ > + if (!data_present) { > + *finished = 1; > + ret = 0; > + goto cleanup; > + } > + input_frame_size = input_codec_context->frame_size; > + /** Initialize the temporary storage for the converted input samples. */ > + if (init_converted_samples(&converted_input_samples, > output_codec_context, input_frame_size)) > + goto cleanup; > + /** > + * Convert the input samples to the desired output sample format. > + * This requires a temporary storage provided by converted_input_samples. > + */ > + if (convert_samples(input_frame->data, converted_input_samples, > input_frame_size, resampler_context)) > + goto cleanup; > + /** Add the converted input samples to the FIFO buffer for later > processing. */ > + if (add_samples_to_fifo(fifo, converted_input_samples, input_frame_size)) > + goto cleanup; > + ret = 0; > + > +cleanup: > + if (converted_input_samples) { > + av_freep(&converted_input_samples[0]); > + free(converted_input_samples); > + } > + if (input_frame) > + uninit_input_frame(input_frame); > + return ret; > +} > + > +/** Deallocate one frame used for writing to the output file. */ > +static void uninit_output_frame(AVFrame *frame) > +{ > + /** > + * Make sure that the frame's data is freed. > + * Otherwise, there will be memory leaks. > + */ > + if (frame->data) > + av_freep(frame->data); > + avcodec_free_frame(&frame); > +} > + > +/** > + * Initialize one input frame for writing to the output file. > + * The frame will be exactly frame_size samples large. > + */ > +static int init_output_frame(AVFrame **frame, > + AVCodecContext *output_codec_context, > + int frame_size) > +{ > + uint8_t *samples; > + int buffer_size; > + int error; > + /** Create a new frame to store the audio samples. */ > + if (!(*frame = avcodec_alloc_frame())) { > + fprintf(stderr, "Could not allocate output frame\n"); > + return AVERROR_EXIT; > + } > + /** Set the frame's parameters, especially its size and format. */ > + avcodec_get_frame_defaults(*frame); > + (*frame)->nb_samples = frame_size; > + (*frame)->format = output_codec_context->sample_fmt; > + /** > + * Determine the amount of memory required to store the desired number > + * of audio samples. > + */ > + buffer_size = av_samples_get_buffer_size(NULL, > output_codec_context->channels, > + frame_size, > output_codec_context->sample_fmt, 0); > + /** Allocate the memory required to store the audio samples. */ > + if (!(samples = av_malloc(buffer_size))) { > + fprintf(stderr, "Could not allocate output samples\n"); > + uninit_output_frame(*frame); > + return AVERROR(ENOMEM); > + } > + /** > + * Make the allocated memory for samples available to the created frame. > + * This call will make sure that the audio frame can hold as many > + * samples as specified using the allocated memory from above. > + */ > + if ((error = avcodec_fill_audio_frame(*frame, > output_codec_context->channels, > + output_codec_context->sample_fmt, > samples, > + buffer_size, 0)) < 0) { > + fprintf(stderr, "Could not fill output frame (error '%s')\n", > get_error_text(error)); > + uninit_output_frame(*frame); > + return error; > + } The proper/recommended way to do this these days is to use the new av_frame_* API (libavutil/frame.h). You allocate the frame with av_frame_alloc(), set the properties as you do now, then call av_frame_get_buffer() to allocate the data. When you're finished you call av_frame_free() to free both the data and the frame. -- Anton Khirnov _______________________________________________ libav-devel mailing list [email protected] https://lists.libav.org/mailman/listinfo/libav-devel
