本文借鉴与雷神的博客:https://blog.youkuaiyun.com/leixiaohua1020/article/details/12980423/
天妒英才啊!!
话不多数,项目中需要用到ffmpeg从内存中读取数据转码,然后输出到内存中,在此记录几个注意事项,避免遇到坑;
从内存中读取数据:
int TEST::openInputFile()
{
int ret = -1;
unsigned int i;
AVFormatContext ifmt_ctx = NULL;
AVFormatContext ofmt_ctx = NULL;
AVInputFormat* in_fmt = NULL;
ifmt_ctx = avformat_alloc_context();
// 这里一定要注意,in_fmt要是不设置,可能会导致avformat_open_input()或者avformat_find_stream_info()失败
// 我使用的是音频转码,故此处填写音频的简称
in_fmt = av_find_input_format("g729");
unsigned int inbufferSize = 20; //
unsigned char* inbuffer = (unsigned char *)av_mallocz(inbufferSize);
memset(inbuffer, 0, inbufferSize);
AVIOContext * pAViIO_in = avio_alloc_context(inbuffer, inbufferSize, 0, this, read_buffer, NULL, NULL);
if (pAViIO_in == NULL)
return -1;
ifmt_ctx->pb = pAViIO_in;
ifmt_ctx->flags = AVFMT_FLAG_CUSTOM_IO;
if ((ret = avformat_open_input(&ifmt_ctx, NULL, in_fmt, NULL)) < 0)
{
printf("Cannot open input file\n");
return ret;
}
if ((ret = avformat_find_stream_info(ifmt_ctx, NULL)) < 0)
{
printf("Cannot find stream information\n");
return ret;
}
/* select the audio stream */
AVCodec *dec;
ret = av_find_best_stream(ifmt_ctx, AVMEDIA_TYPE_AUDIO, -1, -1, &dec, 0);
if (ret < 0)
{
printf("Cannot find a audio stream in the input file\n");
return ret;
}
for (i = 0; i < ifmt_ctx->nb_streams; i++)
{
if (ifmt_ctx->streams[i]->codec->codec_type != AVMEDIA_TYPE_AUDIO)
continue;
AVStream *stream = ifmt_ctx->streams[i];
dec = avcodec_find_decoder(stream->codecpar->codec_id);
if (!dec)
{
printf("Failed to find decoder for stream %d\n", i);
return AVERROR_DECODER_NOT_FOUND;
}
AVCodecContext *codec_ctx;
codec_ctx = stream->codec;
if (!codec_ctx)
{
printf("Fail to allocate the decoder context");
return AVERROR(ENOMEM);
}
ret = avcodec_parameters_to_context(codec_ctx, stream->codecpar);
if (ret < 0)
{
return ret;
}
if (codec_ctx->codec_type == AVMEDIA_TYPE_VIDEO || codec_ctx->codec_type == AVMEDIA_TYPE_AUDIO)
{
/* Open decoder */
ret = avcodec_open2(codec_ctx, dec, NULL);
if (ret < 0)
{
return ret;
}
}
}
return ret;
}
重点一:
in_fmt = av_find_input_format("g729");
这里一定要注意,in_fmt要是不设置,可能会导致avformat_open_input()或者avformat_find_stream_info()失败
我使用的是音频转码,故此处填写音频的简称:简称不确定的话可到ffmpeg源码中查找;adts一般就是aac;g729就是G.729a;
我遇到的问题是:我只转码音频,网上的教程都是一些转码视频的,有些设置在我需求中不适用,之前都没设置这个格式,导致初始化打开文件一直失败;
重点二:
unsigned int inbufferSize = 20; //
unsigned char* inbuffer = (unsigned char *)av_mallocz(inbufferSize);
memset(inbuffer, 0, inbufferSize);
此处inbufferSize 的大小是重点,一般跟帧大小或者音频采样点有关,这个大小就是下面读取数据的回调函数中固定读取数据的大小,视频一般默认是32K,音频aac是1024,MP3是1152;g729a编码比例是160:1,20个字节一般是两帧;
m_inbuffer 是存放读取出的数据的;
重点三:
AVIOContext * pAViIO_in = avio_alloc_context(inbuffer, inbufferSize, 0, this, fill_iobuffer, NULL, NULL);
if (pAViIO_in == NULL)
return -1;
ifmt_ctx->pb = pAViIO_in;
ifmt_ctx->flags = AVFMT_FLAG_CUSTOM_IO;
if ((ret = avformat_open_input(&ifmt_ctx, NULL, in_fmt, NULL)) < 0)
{
printf("Cannot open input file\n");
return ret;
}
借用雷神的原话:关键要在avformat_open_input()之前初始化一个AVIOContext,而且将原本的AVFormatContext的指针pb(AVIOContext类型)指向这个自行初始化AVIOContext。当自行指定了AVIOContext之后,avformat_open_input()里面的URL参数就不起作用了。示例代码开辟了一块空间iobuffer作为AVIOContext的缓存。
fill_iobuffer则是将数据读取至iobuffer的回调函数。fill_iobuffer()形式(参数,返回值)是固定的,是一个回调函数,如下所示(只是个例子,具体怎么读取数据可以自行设计)。
//读取数据的回调函数-------------------------
//AVIOContext使用的回调函数!
//注意:返回值是读取的字节数
//手动初始化AVIOContext只需要两个东西:内容来源的buffer,和读取这个Buffer到FFmpeg中的函数
//回调函数,功能就是:把buf_size字节数据送入buf即可
//在类中定义时应该是static的
int TEST::read_buffer(void * opaque, uint8_t *buf, int bufsize)
{
TEST* pThis = (TEST*)opaque;
if (pThis == NULL)
return -1;
/*
................
*/
return bufsize;
}
输出到内存:
int TEST::openOutputFile()
{
int nRet = 0;
AVStream *out_stream;
AVStream *in_stream;
AVCodecContext *dec_ctx, *enc_ctx;
AVCodec *encoder;
AVOutputFormat *pAVOutputFormat = NULL;
// aac
pAVOutputFormat = av_guess_format("adts", NULL, NULL);
outbufferSize = 1024;
ofmt_ctx = NULL;
//nRet = avformat_alloc_output_context2(&ofmt_ctx, NULL, NULL, "f:/352x288_aac.ts");
nRet = avformat_alloc_output_context2(&ofmt_ctx, pAVOutputFormat, NULL, NULL);
if (nRet < 0)
{
printf("Fail to avformat_alloc_output_context2() \n");
return nRet;
}
for (int i = 0; i < ifmt_ctx->nb_streams; i++)
{
dec_ctx = ifmt_ctx->streams[i]->codec;
if (dec_ctx->codec_type != AVMEDIA_TYPE_AUDIO)
continue;
if (dec_ctx->codec_type == AVMEDIA_TYPE_VIDEO || dec_ctx->codec_type == AVMEDIA_TYPE_AUDIO)
{
AVDictionary *op = NULL;
if (dec_ctx->codec_type == AVMEDIA_TYPE_VIDEO)
{
}
else
{
encoder = avcodec_find_encoder(AV_CODEC_ID_AAC);
if (!encoder)
{
printf("Necessary encoder not found\n");
return AVERROR_INVALIDDATA;
}
out_stream = avformat_new_stream(ofmt_ctx, encoder);
if (!out_stream)
{
printf("Failed allocating output stream\n");
return AVERROR_UNKNOWN;
}
enc_ctx = out_stream->codec;
if (!enc_ctx)
{
printf("Failed to allocate the encoder context\n");
return AVERROR(ENOMEM);
}
// 音频采样率
enc_ctx->sample_rate = 8000;
enc_ctx->channel_layout = select_channel_layout(encoder);
/* take first format from list of supported formats */
enc_ctx->sample_fmt = encoder->sample_fmts[0];
enc_ctx->channels = av_get_channel_layout_nb_channels(enc_ctx->channel_layout);
enc_ctx->time_base.num = 1;
enc_ctx->time_base.den = enc_ctx->sample_rate;
/** Allow the use of the experimental AAC encoder */
enc_ctx->strict_std_compliance = FF_COMPLIANCE_NORMAL;
}
if (ofmt_ctx->oformat->flags & AVFMT_GLOBALHEADER)
enc_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
nRet = avcodec_open2(enc_ctx, encoder, &op);
if (nRet < 0)
{
printf("Cannot open encoder for stream #%u\n", i);
return nRet;
}
nRet = avcodec_parameters_from_context(out_stream->codecpar, enc_ctx);
if (nRet < 0)
{
printf("Failed to copy encoder parameters to output stream #%u\n", i);
return nRet;
}
out_stream->time_base = enc_ctx->time_base;
}
}
outbuffer = (unsigned char*)av_mallocz(outbufferSize);
memset(m_outbuffer, 0, m_outbufferSize);
pAViIO_out = avio_alloc_context(outbuffer, outbufferSize, 1, this, NULL, write_buffer, NULL);
ofmt_ctx->pb = pAViIO_out;
ofmt_ctx->flags = AVFMT_FLAG_CUSTOM_IO;
nRet = avformat_write_header(ofmt_ctx, NULL);
if (nRet < 0)
return nRet;
return nRet;
}
注意事项同上。
写回调函数:
//写数据的回调函数-------------------------
//AVIOContext使用的回调函数!
//注意:返回值是此次保存的字节数
//回调函数,功能就是:把buf_size字节buf数据保存到另外的缓冲区即可,不要在此函数中执行业务,可能造成阻塞
int TEST::write_buffer(void *opaque, uint8_t *buf, int buf_size)
{
TEST* pThis = (TEST*)opaque;
if (pThis == NULL)
return -1;
/*
................
*/
return buf_size;
}
至此完毕,其他部分代码没有什么变化,一步步调用即可;