Created
February 17, 2017 18:32
-
-
Save anonymous/3b67a65becf5d1e2e4b304a4e94af651 to your computer and use it in GitHub Desktop.
FFMPEG open device decode/encode audio to file
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 ); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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]