Skip to content

Instantly share code, notes, and snippets.

Created February 17, 2017 18:32
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save anonymous/3b67a65becf5d1e2e4b304a4e94af651 to your computer and use it in GitHub Desktop.
Save anonymous/3b67a65becf5d1e2e4b304a4e94af651 to your computer and use it in GitHub Desktop.
FFMPEG open device decode/encode audio to file
extern "C"
{
#define __STDC_CONSTANT_MACROS
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libavdevice/avdevice.h>
#include <libswresample/swresample.h>
#include <libavutil/log.h>
#include <libavutil/avassert.h>
}
#include <iostream>
#include <cstdio>
#include <ctime> // clock_gettime
static unsigned int verbose = 0;
static const unsigned int VVERBOSE = 2;
static const unsigned int VVVERBOSE = 3;
typedef struct
{
AVFormatContext * fmt_ctx;
AVCodecContext * codec_ctx;
AVCodec * codec;
SwrContext * swr_ctx;
AVFrame * frame;
} StreamContext;
StreamContext streamIn = { NULL, NULL, NULL, NULL, NULL };
StreamContext streamOut = { NULL, NULL, NULL, NULL, NULL };
static inline void GetTimeSpec( struct timespec & ts )
{
struct timespec this_time;
if ( clock_gettime( CLOCK_MONOTONIC, &this_time ) == 0 )
{
ts = this_time;
}
/// else FIXME maybe what to do if kernel call fails ???
}
static inline double DiffTimeSpecMicroSeconds( const struct timespec * end, const struct timespec * start )
{
double delta = 0.0;
/* adjust for roll over move 1 second to 1000000000 ns */
if ( ( end->tv_nsec - start->tv_nsec ) < 0 )
{
// - 1 assumes end later than start
delta = ( end->tv_sec - start->tv_sec - 1 ) * 1000000.0;
delta += ( 1000000000 + end->tv_nsec - start->tv_nsec ) / 1000.0;
}
else
{
delta = ( end->tv_sec - start->tv_sec ) * 1000000.0;
delta += ( end->tv_nsec - start->tv_nsec ) / 1000.0;
}
return delta;
}
static inline int DiffTimeSpecSeconds( const struct timespec * end, const struct timespec * start )
{
return static_cast<int>( DiffTimeSpecMicroSeconds( end, start ) / 1000000.0 );
}
/* check that a given sample format is supported by the encoder */
static int check_sample_fmt(AVCodec *codec, enum AVSampleFormat sample_fmt)
{
const enum AVSampleFormat *p = codec->sample_fmts;
while (*p != AV_SAMPLE_FMT_NONE)
{
if ( *p == sample_fmt )
return 1;
p++;
}
return 0;
}
/* select layout with the highest channel count */
static int select_channel_layout(AVCodec *codec)
{
const uint64_t *p;
uint64_t best_ch_layout = 0;
int best_nb_channels = 0;
if (!codec->channel_layouts)
return AV_CH_LAYOUT_STEREO;
p = codec->channel_layouts;
while (*p) {
int nb_channels = av_get_channel_layout_nb_channels(*p);
if (nb_channels > best_nb_channels) {
best_ch_layout = *p;
best_nb_channels = nb_channels;
}
p++;
}
return best_ch_layout;
}
/**
* Print an error string describing the errorCode to stderr.
*/
int printError( const std::string msg, int errorCode )
{
std::cerr << "ERROR - " << msg;
if ( errorCode != 0 )
{
const size_t bufsize = AV_ERROR_MAX_STRING_SIZE ;
char buf[bufsize];
/**
* Put a description of the AVERROR code errnum in errbuf.
* In case of failure the global variable errno is set to indicate the
* error. Even in case of failure av_strerror() will print a generic
* error message indicating the errnum provided to errbuf.
*
* @param errnum error code to describe
* @param errbuf buffer to which description is written
* @param errbuf_size the size in bytes of errbuf
* @return 0 on success, a negative value if a description for errnum
* cannot be found
*
int av_strerror(int errnum, char *errbuf, size_t errbuf_size);
*/
if ( av_strerror( errorCode, buf, bufsize ) != 0 )
{
strcpy( buf, "UNKNOWN_ERROR" );
}
std::cerr << "(printError) ( code = " << errorCode << ": " << buf << "))" << std::endl;
}
std::cerr << std::endl;
return errorCode;
}
/**
* Find the first audio stream and returns its index. If there is no audio stream returns -1.
*/
int findAudioStream( const AVFormatContext * formatCtx )
{
int audioStreamIndex = -1;
for( size_t i = 0; i < formatCtx->nb_streams; ++i )
{
// Use the first audio stream we can find.
// NOTE: There may be more than one, depending on the file.
if( formatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO )
{
audioStreamIndex = i;
break;
}
}
return audioStreamIndex;
}
/*
* Print information about the input file and the used codec.
*/
void printStreamInformation( const AVCodec * codec, const AVCodecContext * codecCtx, int audioStreamIndex )
{
fprintf( stderr, "Codec: %s\n", codec->long_name );
if( codec->sample_fmts != NULL )
{
fprintf( stderr, "Supported sample formats: " );
for( int i = 0; codec->sample_fmts[i] != -1; ++i )
{
fprintf( stderr, "%s", av_get_sample_fmt_name( codec->sample_fmts[i] ) );
if( codec->sample_fmts[i + 1] != -1 )
{
fprintf( stderr, ", " );
}
}
fprintf( stderr, "\n" );
}
fprintf( stderr, "---------\n" );
fprintf( stderr, "Stream: %7d\n", audioStreamIndex );
fprintf( stderr, "Sample Format: %7s\n", av_get_sample_fmt_name( codecCtx->sample_fmt ) );
fprintf( stderr, "Sample Rate: %7d\n", codecCtx->sample_rate );
fprintf( stderr, "Sample Size: %7d\n", av_get_bytes_per_sample( codecCtx->sample_fmt ) );
fprintf( stderr, "Channels: %7d\n", codecCtx->channels );
fprintf( stderr, "Planar: %7d\n", av_sample_fmt_is_planar( codecCtx->sample_fmt ) );
fprintf( stderr, "Float Output: %7s\n", av_sample_fmt_is_planar( codecCtx->sample_fmt ) ? "yes" : "no" );
}
AVFrame * alloc_audio_frame( enum AVSampleFormat sample_fmt, uint64_t channel_layout, int sample_rate, int nb_samples )
{
int err = 0;
AVFrame * frame = av_frame_alloc();
if ( frame )
{
frame->format = sample_fmt;
frame->channel_layout = channel_layout;
frame->sample_rate = sample_rate;
frame->nb_samples = nb_samples;
if ( nb_samples )
{
if ( ( err = av_frame_get_buffer( frame, 0 ) ) < 0 )
{
printError( "av_frame_get_buffer", err );
av_frame_free( &frame );
frame = NULL;
}
}
}
else
{
std::cerr << "ERROR - av_frame_alloc" << std::endl;
}
return frame;
}
int OpenInputDeviceContext( const std::string format, const std::string devname )
{
int err = 0;
int stream_found;
avdevice_register_all();
AVInputFormat * av_in_fmt = av_find_input_format( format.c_str() );
if ( avformat_open_input( &streamIn.fmt_ctx, devname.c_str(), av_in_fmt, NULL ) != 0 )
{
std::string err_msg = "ERROR - opening the device ";
err_msg += devname;
return printError( err_msg.c_str(), err );
}
if ( avformat_find_stream_info( streamIn.fmt_ctx, NULL ) < 0 )
{
avformat_close_input( &streamIn.fmt_ctx );
return printError( "finding stream info", err );
}
// Find the audio stream
stream_found = av_find_best_stream( streamIn.fmt_ctx, AVMEDIA_TYPE_AUDIO, -1, -1, &streamIn.codec , 0 );
if ( stream_found < 0 )
{
avformat_close_input( &streamIn.fmt_ctx );
return printError( "None of the available streams are audio streams.", stream_found );
}
if ( !streamIn.codec )
{
streamIn.codec = avcodec_find_decoder( streamIn.fmt_ctx->streams[ stream_found ]->codecpar->codec_id );
if ( streamIn.codec == NULL )
{
std::cerr << "ERROR - Decoder not found. The codec is not supported." << std::endl;
avformat_close_input( &streamIn.fmt_ctx );
return -1;
}
}
streamIn.codec_ctx = avcodec_alloc_context3( streamIn.codec );
if ( streamIn.codec_ctx == NULL )
{
avformat_close_input( &streamIn.fmt_ctx );
std::cerr << "ERROR - Could not allocate a decoding context." << std::endl;
return -1;
}
/** initialize the stream parameters with demuxer information */
if ( avcodec_parameters_to_context( streamIn.codec_ctx, streamIn.fmt_ctx->streams[ stream_found ]->codecpar ) < 0 )
{
avcodec_close( streamIn.codec_ctx );
avcodec_free_context( &streamIn.codec_ctx );
avformat_close_input( &streamIn.fmt_ctx );
return printError( "Setting codec context parameters", err );
}
// Explicitly request non planar data.
// streamIn.codec_ctx->request_sample_fmt = av_get_alt_sample_fmt( streamIn.codec_ctx->sample_fmt, 0 );
if ( avcodec_open2( streamIn.codec_ctx, streamIn.codec_ctx->codec, NULL ) != 0 )
{
avcodec_close( streamIn.codec_ctx );
avcodec_free_context( &streamIn.codec_ctx );
avformat_close_input( &streamIn.fmt_ctx );
std::cerr << "ERROR - Couldn't open the context with the decoder" << std::endl;
return -1;
}
if ( verbose > VVERBOSE )
{
std::cout << "This stream has " << streamIn.codec_ctx->channels
<< " channels and a sample rate of " << streamIn.codec_ctx->sample_rate << "Hz\n"
<< "The data is in the format " << av_get_sample_fmt_name( streamIn.codec_ctx->sample_fmt )
<< std::endl;
}
return stream_found;
}
static int simple_sws()
{
int err = 0;
streamOut.swr_ctx = swr_alloc();
if ( !streamOut.swr_ctx )
{
std::cerr << "ERROR - swr_alloc failed." << std::endl;
return -1;
}
//return printError( "finding stream info", err );
av_opt_set_channel_layout( streamOut.swr_ctx, "in_channel_layout", AV_CH_LAYOUT_STEREO, 0 );
av_opt_set_int ( streamOut.swr_ctx, "in_channel_count", streamIn.codec_ctx->channels, 0 );
av_opt_set_int ( streamOut.swr_ctx, "in_sample_rate", streamIn.codec_ctx->sample_rate, 0 );
av_opt_set_sample_fmt ( streamOut.swr_ctx, "in_sample_fmt", streamIn.codec_ctx->sample_fmt, 0 );
av_opt_set_channel_layout( streamOut.swr_ctx, "out_channel_layout", AV_CH_LAYOUT_STEREO, 0 );
av_opt_set_int ( streamOut.swr_ctx, "out_channel_count", streamOut.codec_ctx->channels, 0 );
av_opt_set_int ( streamOut.swr_ctx, "out_sample_rate", streamOut.codec_ctx->sample_rate, 0 );
av_opt_set_sample_fmt ( streamOut.swr_ctx, "out_sample_fmt", AV_SAMPLE_FMT_FLTP, 0 );
if ( ( err = swr_init( streamOut.swr_ctx ) ) < 0 )
{
printError( "swr_init failed", err );
}
return err;
}
int OpenOutputContex( const std::string type, const std::string fname, const enum AVCodecID codec_id )
{
int err = 0;
AVDictionary * av_dict_opts = NULL;
AVStream * av_stream = NULL;
/**
* Allocate an AVFormatContext for an output format.
* avformat_free_context() can be used to free the context and
* everything allocated by the framework within it.
*
* @param *ctx is set to the created format context, or to NULL in
* case of failure
* @param oformat format to use for allocating the context, if NULL
* format_name and filename are used instead
* @param format_name the name of output format to use for allocating the
* context, if NULL filename is used instead
* @param filename the name of the filename to use for allocating the
* context, may be NULL
* @return >= 0 in case of success, a negative AVERROR code in case of
* failure
*
* int avformat_alloc_output_context2(AVFormatContext **ctx, AVOutputFormat *oformat,
* const char *format_name, const char *filename);
*/
avformat_alloc_output_context2( &streamOut.fmt_ctx, NULL, type.c_str(), fname.c_str() ); //(char*)"mkv", fname.c_str() );
if ( !streamOut.fmt_ctx )
{
std::cerr << "ERROR - avformat_alloc_output_context2" << std::endl;
return -1;
}
streamOut.codec = avcodec_find_encoder( codec_id );
if ( !streamOut.codec )
{
std::cerr << "ERROR - avcodec_find_encoder" << std::endl;
return -1;
}
/**
* Allocate an AVCodecContext and set its fields to default values. The
* resulting struct should be freed with avcodec_free_context().
*
* @param codec if non-NULL, allocate private data and initialize defaults
* for the given codec. It is illegal to then call avcodec_open2()
* with a different codec.
* If NULL, then the codec-specific defaults won't be initialized,
* which may result in suboptimal default settings (this is
* important mainly for encoders, e.g. libx264).
*
* @return An AVCodecContext filled with default values or NULL on failure.
*
* AVCodecContext *avcodec_alloc_context3(const AVCodec *codec);
*/
streamOut.codec_ctx = avcodec_alloc_context3( streamOut.codec );
if ( !streamOut.codec_ctx )
{
std::cerr << "ERROR - avcodec_alloc_context3" << std::endl;
return -1;
}
///
/// SETUP CODEC
///
streamOut.codec_ctx->bit_rate = 64000; // streamIn.codec_ctx->bit_rate;
streamOut.codec_ctx->sample_fmt = AV_SAMPLE_FMT_FLTP;
if ( !check_sample_fmt( streamOut.codec, streamOut.codec_ctx->sample_fmt ) )
{
std::cerr << "ERROR - check_sample_fmt failed." << std::endl;
return -1;
}
streamOut.codec_ctx->sample_rate = streamIn.codec_ctx->sample_rate;
streamOut.codec_ctx->channel_layout = select_channel_layout( streamOut.codec );
streamOut.codec_ctx->channels = av_get_channel_layout_nb_channels( streamIn.codec_ctx->channel_layout );
streamOut.codec_ctx->time_base = (AVRational){ 1, streamOut.codec_ctx->sample_rate };
/// experimental AAC encoder
streamOut.codec_ctx->strict_std_compliance = FF_COMPLIANCE_EXPERIMENTAL;
/**
* Add a new stream to a media file.
*
* When demuxing, it is called by the demuxer in read_header(). If the
* flag AVFMTCTX_NOHEADER is set in s.ctx_flags, then it may also
* be called in read_packet().
*
* When muxing, should be called by the user before avformat_write_header().
*
* User is required to call avcodec_close() and avformat_free_context() to
* clean up the allocation by avformat_new_stream().
*
* @param s media file handle
* @param c If non-NULL, the AVCodecContext corresponding to the new stream
* will be initialized to use this codec. This is needed for e.g. codec-specific
* defaults to be set, so codec should be provided if it is known.
*
* @return newly created stream or NULL on error.
*
* AVStream *avformat_new_stream(AVFormatContext *s, const AVCodec *c);
*/
av_stream = avformat_new_stream( streamOut.fmt_ctx, streamOut.codec );
if ( !av_stream )
{
std::cerr << "ERROR - avformat_new_stream" << std::endl;
return -1;
}
/**
* Fill the parameters struct based on the values from the supplied codec
* context. Any allocated fields in par are freed and replaced with duplicates
* of the corresponding fields in codec.
*
* @return >= 0 on success, a negative AVERROR code on failure
*/
err = avcodec_parameters_from_context( av_stream->codecpar, streamOut.codec_ctx );
if ( err < 0 )
{
return printError( "avcodec_parameters_from_context", err );
}
/**
* Initialize the AVCodecContext to use the given AVCodec. Prior to using this
* function the context has to be allocated with avcodec_alloc_context3().
*
* The functions avcodec_find_decoder_by_name(), avcodec_find_encoder_by_name(),
* avcodec_find_decoder() and avcodec_find_encoder() provide an easy way for
* retrieving a codec.
*
* @warning This function is not thread safe!
*
* @note Always call this function before using decoding routines (such as
* @ref avcodec_receive_frame()).
*
* @code
* avcodec_register_all();
* av_dict_set(&opts, "b", "2.5M", 0);
* codec = avcodec_find_decoder(AV_CODEC_ID_H264);
* if (!codec)
* exit(1);
*
* context = avcodec_alloc_context3(codec);
*
* if (avcodec_open2(context, codec, opts) < 0)
* exit(1);
* @endcode
*
* @param avctx The context to initialize.
* @param codec The codec to open this context for. If a non-NULL codec has been
* previously passed to avcodec_alloc_context3() or
* for this context, then this parameter MUST be either NULL or
* equal to the previously passed codec.
* @param options A dictionary filled with AVCodecContext and codec-private options.
* On return this object will be filled with options that were not found.
*
* @return zero on success, a negative value on error
* @see avcodec_alloc_context3(), avcodec_find_decoder(), avcodec_find_encoder(),
* av_dict_set(), av_opt_find().
*
* int avcodec_open2(AVCodecContext *avctx, const AVCodec *codec, AVDictionary **options);
*/
err = avcodec_open2( streamOut.codec_ctx, streamOut.codec, &av_dict_opts );
if ( err < 0 )
{
return printError( "avcodec_open2", err );
}
if ( streamOut.fmt_ctx->oformat->flags & AVFMT_GLOBALHEADER )
streamOut.fmt_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
av_dump_format( streamOut.fmt_ctx, 0, fname.c_str(), 1 );
if ( !( streamOut.fmt_ctx->oformat->flags & AVFMT_NOFILE ) )
{
err = avio_open( &streamOut.fmt_ctx->pb, fname.c_str(), AVIO_FLAG_WRITE );
if ( err < 0 )
{
return printError( "avio_open", err );
}
}
err = avformat_write_header( streamOut.fmt_ctx, &av_dict_opts );
if ( err < 0 )
{
return printError( "avformat_write_header", err );
}
return err;
}
int EncodeAndWriteAudioFrame( AVFrame * av_frame )
{
static struct timespec tspec_now = { 0, 0 };
static struct timespec tspec_tmp = { 0, 0 };
int err = 0;
AVPacket av_pkt;
av_init_packet( &av_pkt );
GetTimeSpec( tspec_tmp );
if ( ( err = avcodec_send_frame( streamOut.codec_ctx, av_frame ) ) < 0 )
{
return printError( "Svcodec_send_frame ENCODER", err );
}
if ( verbose > VVVERBOSE )
{
GetTimeSpec( tspec_now );
std::cout << "SENT FRAME TO ENCODER took " << DiffTimeSpecMicroSeconds( &tspec_now, &tspec_tmp ) << std::endl;
}
do
{
GetTimeSpec( tspec_tmp );
/**
* Read encoded data from the encoder.
*
* @param avctx codec context
* @param avpkt This will be set to a reference-counted packet allocated by the
* encoder. Note that the function will always call
* av_frame_unref(frame) before doing anything else.
* @return 0 on success, otherwise negative error code:
* AVERROR(EAGAIN): output is not available right now - user must try
* to send input
* AVERROR_EOF: the encoder has been fully flushed, and there will be
* no more output packets
* AVERROR(EINVAL): codec not opened, or it is an encoder
* other errors: legitimate decoding errors
*
* int avcodec_receive_packet(AVCodecContext *avctx, AVPacket *avpkt);
*/
err = avcodec_receive_packet( streamOut.codec_ctx, &av_pkt );
if ( err == AVERROR(EAGAIN) && verbose > VVVERBOSE ) std::cout << "GOT EAGIN FROM ENCODER" << std::endl;
if ( err == 0 )
{
if ( verbose > VVVERBOSE )
{
GetTimeSpec( tspec_now );
std::cout << "GOT PACKET FROM ENCODER took " << DiffTimeSpecMicroSeconds( &tspec_now, &tspec_tmp ) << std::endl;
}
if ( av_pkt.pts != AV_NOPTS_VALUE )
av_pkt.pts = av_rescale_q( av_pkt.pts, streamOut.codec_ctx->time_base, streamOut.fmt_ctx->streams[ 0 ]->time_base );
if ( av_pkt.dts != AV_NOPTS_VALUE )
av_pkt.dts = av_rescale_q( av_pkt.dts, streamOut.codec_ctx->time_base, streamOut.fmt_ctx->streams[ 0 ]->time_base );
/// pkt.pts = av_rescale_q_rnd(pkt.pts, in_stream->time_base, out_stream->time_base, AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX);
/// pkt.dts = av_rescale_q_rnd(pkt.dts, in_stream->time_base, out_stream->time_base, AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX);
/// pkt.duration = av_rescale_q(pkt.duration, in_stream->time_base, out_stream->time_base);
err = av_interleaved_write_frame( streamOut.fmt_ctx, &av_pkt );
if ( err < 0 )
{
return printError( "av_interleaved_write_frame", err );
}
}
else if ( ! ( err == AVERROR(EAGAIN) || err == AVERROR_EOF ) )
{
return printError( "avcodec_receive_packet", err );
}
} while ( err == 0 ); /// && !frame_done );
return err;
}
int main( int argc, char * argv[] )
{
int audioStreamIndex = -1;
int err = 0;
int dst_nb_samples;
static int64_t PTS = 0;
static struct timespec tspec_start = { 0, 0 };
static struct timespec tspec_now = { 0, 0 };
static struct timespec tspec_last = { 0, 0 };
static struct timespec tspec_top = { 0, 0 };
static struct timespec tspec_tmp = { 0, 0 };
static unsigned int frames_read = 0;
static unsigned int frames_encoded = 0;
static unsigned int frames_decoded = 0;
static int run_time_seconds = 0;
static unsigned int max_frames = 0;
static int argv_index = 1 ;
static bool running = true;
static unsigned int time_used_us = 0;
std::string input_device = "hw:0,0";
AVPacket av_packet_in;
// uint8_t inbuf[AUDIO_INBUF_SIZE + AV_INPUT_BUFFER_PADDING_SIZE];
while ( argv_index < argc )
{
if ( strcasecmp( argv[argv_index], "-t" ) == 0 && ( argc > argv_index ) )
{
unsigned int tmp;
argv_index++; // param
if ( sscanf( argv[argv_index], "%u", &tmp ) == 1 )
{
run_time_seconds = tmp;
}
else
std::cerr << "OPTION -t ignored as not unsigned integer value" << std::endl;
}
else if ( strcasecmp( argv[argv_index], "-c" ) == 0 && ( argc > argv_index ) )
{
unsigned int tmp;
argv_index++; // param
if ( sscanf( argv[argv_index], "%u", &tmp ) == 1 )
{
max_frames = tmp;
}
else
{
std::cerr << "OPTION -c ignored as not unsigned integer value" << std::endl;
}
}
else if ( strcasecmp( argv[argv_index], "-i" ) == 0 && ( argc > argv_index ) )
{
argv_index++; // param
input_device = argv[argv_index];
std::cout << "OPTION -i Input device change to " << input_device << std::endl;
}
else if ( strcasecmp( argv[argv_index], "-vvv" ) == 0 )
{
verbose = VVVERBOSE;
}
else if ( strcasecmp( argv[argv_index], "-vv" ) == 0 )
{
verbose = VVERBOSE;
}
else if ( strcasecmp( argv[argv_index], "-v" ) == 0 )
{
++verbose;
}
argv_index++; // NEXT
} // while ( argv_index < argc )
if ( verbose )
{
if ( max_frames )
std::cout << "OPTION -c max frames set to " << max_frames << std::endl;
if ( run_time_seconds )
std::cout << "OPTION -t run time seconds set to " << run_time_seconds << std::endl;
std::cout << "OPTION -v verbose level set to " << verbose << std::endl;
}
GetTimeSpec( tspec_start );
// Initialize the libavformat. This registers all muxers, demuxers and protocols.
av_register_all();
if ( verbose )
av_log_set_level( AV_LOG_DEBUG );
else
av_log_set_level( AV_LOG_ERROR );
// AV_LOG_FATAL
// AV_LOG_PANIC
// AV_LOG_QUIET
audioStreamIndex = OpenInputDeviceContext( "alsa", input_device.c_str() );
if ( audioStreamIndex < 0 )
{
// No audio stream was found.
fprintf( stderr, "None of the available %d streams are audio streams.\n", streamIn.fmt_ctx->nb_streams );
avformat_close_input( &streamIn.fmt_ctx );
return -1;
}
// Print some intersting file information.
if ( verbose > VVERBOSE ) printStreamInformation( streamIn.codec, streamIn.codec_ctx, audioStreamIndex );
if ( OpenOutputContex( "mp4", "test.mpa", AV_CODEC_ID_AAC ) < 0 )
{
std::cerr << "ERROR - OpenOutputContex failed." << std::endl;
return -1;
}
if ( verbose > VVERBOSE ) printStreamInformation( streamOut.codec, streamOut.codec_ctx, audioStreamIndex );
if ( simple_sws() )
{
std::cerr << "ERROR - simple_sws failed." << std::endl;
return -1;
}
if ( ( streamIn.frame = av_frame_alloc() ) == NULL )
{
swr_free( &streamOut.swr_ctx );
avcodec_close( streamIn.codec_ctx );
avcodec_free_context( &streamIn.codec_ctx );
avformat_close_input( &streamIn.fmt_ctx );
avcodec_close( streamOut.codec_ctx );
avcodec_free_context( &streamOut.codec_ctx );
avformat_close_input( &streamOut.fmt_ctx );
std::cerr << "ERROR - av_frame_alloc streamIn.frame failed." << std::endl;
return -1;
}
streamOut.frame = alloc_audio_frame( streamOut.codec_ctx->sample_fmt, streamOut.codec_ctx->channel_layout, streamOut.codec_ctx->sample_rate, streamOut.codec_ctx->frame_size );
if ( !streamOut.frame )
{
av_frame_free( &streamIn.frame );
swr_free( &streamOut.swr_ctx );
avcodec_close( streamIn.codec_ctx );
avcodec_free_context( &streamIn.codec_ctx );
avformat_close_input( &streamIn.fmt_ctx );
avcodec_close( streamOut.codec_ctx );
avcodec_free_context( &streamOut.codec_ctx );
avformat_close_input( &streamOut.fmt_ctx );
std::cerr << "ERROR - alloc_audio_frame streamOut.frame failed." << std::endl;
return -1;
}
// Set default values.
av_init_packet( &av_packet_in );
av_packet_in.data = NULL; /// DEMUXER will fill the data
av_packet_in.size = 0;
// first read always mostly empty
if ( ( err = av_read_frame( streamIn.fmt_ctx, &av_packet_in ) ) != 0 )
{
running = false;
}
GetTimeSpec( tspec_last );
while ( running )
{
GetTimeSpec( tspec_top );
if ( verbose > VVVERBOSE ) std::cout << "READ ANOTHER FRAME FROM THE INPUT" << std::endl;
if ( ( err = av_read_frame( streamIn.fmt_ctx, &av_packet_in ) ) != 0 )
{
if ( err != AVERROR_EOF )
{
// Something went wrong.
printError( "ERROR - Input (av_read_frame) error", err );
}
else
std::cout << "REACHED EOF ON INPUT" << std::endl;
break;
}
if ( verbose > VVVERBOSE )
{
GetTimeSpec( tspec_now );
std::cout << "Input Packet read frame took = "
<< DiffTimeSpecMicroSeconds( &tspec_now , &tspec_top )
<< " microseconds"
<< std::endl;
}
// Does the packet belong to the correct stream?
if ( av_packet_in.stream_index != audioStreamIndex )
{
if ( verbose ) std::cout << "Found packet from a stream that is not audio" << std::endl;
continue;
}
++frames_read;
GetTimeSpec( tspec_tmp );
if ( ( err = avcodec_send_packet( streamIn.codec_ctx, &av_packet_in ) ) == 0 )
{
if ( verbose > VVVERBOSE )
{
GetTimeSpec( tspec_now );
std::cout << "Send the packet to the DECODER took " << DiffTimeSpecMicroSeconds( &tspec_now, &tspec_tmp ) << std::endl;
}
// The packet was sent successfully. We don't need it anymore.
// => Free the buffers used by the frame and reset all fields.
}
else
{
printError( "ERROR - avcodec_send_packet DECODER", err );
break;
}
do
{
GetTimeSpec( tspec_tmp );
// EAGAIN means we need to send before receiving again. So thats not an error.
err = avcodec_receive_frame( streamIn.codec_ctx, streamIn.frame );
if ( err && err != AVERROR( EAGAIN ) )
{
printError( "ERROR - avcodec_receive_frame DECODER", err );
break;
}
if ( err != AVERROR( EAGAIN ) )
{
GetTimeSpec( tspec_now );
if ( verbose > VVVERBOSE ) std::cout << "GOT FRAME FROM DECODER took " << DiffTimeSpecMicroSeconds( &tspec_now, &tspec_tmp ) << std::endl;
++frames_decoded;
}
///
/// GOT A FRAME
///
streamOut.frame->pts = PTS;
PTS += streamIn.frame->nb_samples * 2;
/* convert samples from native format to destination codec format, using the resampler */
/* compute destination number of samples */
dst_nb_samples = av_rescale_rnd( swr_get_delay( streamOut.swr_ctx, streamOut.codec_ctx->sample_rate ) + streamIn.frame->nb_samples,
streamOut.codec_ctx->sample_rate, streamOut.codec_ctx->sample_rate, AV_ROUND_UP);
av_assert0(dst_nb_samples == streamIn.frame->nb_samples);
/// /* when we pass a frame to the encoder, it may keep a reference to it
/// * internally; make sure we do not overwrite it here
/// */
/// if ( ( err = av_frame_make_writable( streamOut.frame ) ) < 0 )
/// {
/// printError( "ERROR - av_frame_make_writable", err );
/// break;
/// }
GetTimeSpec( tspec_tmp );
if ( ( err = swr_convert( streamOut.swr_ctx, streamOut.frame->data, dst_nb_samples, (const uint8_t **)streamIn.frame->extended_data, streamIn.frame->nb_samples ) ) < 0 )
{
printError( "ERROR - swr_convert", err );
break;
}
if ( verbose > VVVERBOSE )
{
GetTimeSpec( tspec_now );
std::cout << "SWR took " << DiffTimeSpecMicroSeconds( &tspec_now, &tspec_tmp ) << std::endl;
}
///
/// ENCODE
///
err = EncodeAndWriteAudioFrame( streamOut.frame );
frames_encoded++;
GetTimeSpec( tspec_now ); // end frame time
if ( verbose > VVVERBOSE && err == AVERROR(EAGAIN) )
{
std::cout << "EncodeAndWriteAudioFrame returned EAGAIN" << std::endl;
}
} while ( !err && ( err != AVERROR( EAGAIN ) ) );
if ( max_frames ) running = ( frames_encoded < max_frames );
if ( running && run_time_seconds ) running = ( DiffTimeSpecSeconds( &tspec_now, &tspec_start ) < run_time_seconds );
time_used_us = DiffTimeSpecMicroSeconds( &tspec_now , &tspec_last );
if ( verbose > VVVERBOSE ) std::cout << "Total time_used_us = " << time_used_us << std::endl;
if ( time_used_us < 16000 ) usleep( 16600 - time_used_us );
// tspec_last = tspec_now; // save end frame time
GetTimeSpec( tspec_last );
}
if ( verbose )
std::cout << "\n\nTotal time used was " << DiffTimeSpecMicroSeconds( &tspec_now , &tspec_start ) / 1000000 << " seconds"
<< " frames read from input was " << frames_read
<< " frames decoded was " << frames_decoded
<< " frames encoded was " << frames_encoded
<< "\n" << std::endl;
///
/// DRAIN ENCODER
///
do
{
err = EncodeAndWriteAudioFrame( NULL );
} while ( !err );
if ( err && err != AVERROR_EOF ) printError( "DRAIN avcodec_receive_frame", err );
sleep( 1 );
if ( verbose ) std::cout << "Write the trailer" << std::endl;
if ( ( err = av_write_trailer( streamOut.fmt_ctx ) ) < 0)
{
printError( "ERROR - av_write_trailer", err );
}
swr_free( &streamOut.swr_ctx );
av_frame_free( &streamOut.frame );
avcodec_close( streamIn.codec_ctx );
avcodec_close( streamOut.codec_ctx );
avcodec_free_context( &streamIn.codec_ctx );
avcodec_free_context( &streamOut.codec_ctx );
avformat_close_input( &streamIn.fmt_ctx );
avformat_close_input( &streamOut.fmt_ctx );
}
@LinuxwitChdoCtOr
Copy link

g++ -g -std=c++0x -Wall -Wextra -I/usr/include/ffmpeg record_audio_to_file.cpp -lavformat -lavcodec -lavutil -lswscale -lavdevice -lm -lrt -o record_audio_to_file

-vvv very very verbose
-vv very verbose
each [-v] increase the verbose level
-t [seconds] -c [max frames]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment