我的前两篇文章讲到了MP3和AAC文件的编码与生成,下面接着讲一讲如何解码它们。经过前面一段时间的积累,我们也对MP3和AAC有了初步的了解,本文直接以用法入题。
相关接口:
结构介绍:
编解码器上下文,这个结构保存当前打开的编解码器、设置的参数,摘录部分成员如下:
struct AVCodecContext
{
enum AVMediaType codec_type; /* see AVMEDIA_TYPE_xxx */
const struct AVCodec *codec;
enum AVCodecID codec_id; /* see AV_CODEC_ID_xxx */
unsigned int codec_tag;
int64_t bit_rate;
int global_quality;
int compression_level;
AVRational time_base;
int width, height;
int coded_width, coded_height;
int gop_size;
enum AVPixelFormat pix_fmt;
int max_b_frames;
int has_b_frames;
/* audio only */
int sample_rate; ///< samples per second
int channels; ///< number of audio channels
enum AVSampleFormat sample_fmt; ///< sample format
uint64_t channel_layout;
AVRational framerate;
enum AVPixelFormat sw_pix_fmt;
};
媒体文件解析器,这个结构保存了当前解析媒体文体的上下文状态,摘录部分成员如下:
struct AVCodecParserContext
{
const struct AVCodecParser *parser;
int64_t frame_offset; /* offset of the current frame */
int64_t cur_offset; /* current offset
(incremented by each av_parser_parse()) */
int64_t next_frame_offset; /* offset of the next frame */
/* video info */
int pict_type; /* XXX: Put it back in AVCodecContext. */
int repeat_pict; /* XXX: Put it back in AVCodecContext. */
int64_t pts; /* pts of the current frame */
int64_t dts; /* dts of the current frame */
/* private data */
int64_t last_pts;
int64_t last_dts;
int fetch_timestamp;
int width;
int height;
int coded_width;
int coded_height;
int format;
};
函数介绍:
根据编解码器ID,返回对应的编解码器名称。
const char *avcodec_get_name(enum AVCodecID id);
根据解码器名称查找注册的解码器。
const AVCodec *avcodec_find_decoder_by_name(const char *name);
根据编解码器指针,初使化编解码器上下文。
AVCodecContext *avcodec_alloc_context3(const AVCodec *codec);
释放编解码器上下文。
void avcodec_free_context(AVCodecContext **avctx);
绑定编解码器到编解码器上下文,并打开相关资源。
int avcodec_open2(AVCodecContext *avctx, const AVCodec *codec, AVDictionary **options);
根据编解码器初使化解析器上下文。
AVCodecParserContext *av_parser_init(int codec_id);
关闭解析器上下文。
void av_parser_close(AVCodecParserContext *s);
解析一个包。
返回成功处理的缓冲区的长度,下一次处理应该缓冲区的后续位置继续处理,其效果相当于解封装时使用的av_read_frame()函数。
这个函数总是返回非负,因而无需处理异常的情况。
int av_parser_parse2(AVCodecParserContext *s,
AVCodecContext *avctx,
uint8_t **poutbuf, int *poutbuf_size,
const uint8_t *buf, int buf_size,
int64_t pts, int64_t dts,
int64_t pos);
发送待解码数据。
返回值:0表示成功,其他表示出错。
可能的出错值:
AVERROR(EAGAIN) 表示发送缓冲区已满,需要等待avcodec_receive_frame()提取。
AVERROR_EOF 表示解析器已经关闭。
int avcodec_send_packet(AVCodecContext *avctx, const AVPacket *avpkt);
接收解码数据。
返回值:0表示成功,其他表示出错。
可能的出错值:
AVERROR(EAGAIN) 表示接收缓冲区为空,无数据可提取。
AVERROR_EOF 表示接收缓冲区为空。
int avcodec_receive_frame(AVCodecContext *avctx, AVFrame *frame);
代码举例:
下面这个例子演示了读取test.mp3文件并解码,将结果写为PCM文件的过程。只要修改相应的解码器为AAC,和打开的文件名,就能支持AAC。代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
extern "C"
{
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libavutil/channel_layout.h>
}
const char* getSampleFormatName(enum AVSampleFormat emSampleFormat);
bool decode(AVCodecContext* pCodecCTX, const AVPacket* pPacket, AVFrame* pFrame, FILE* pFile);
int main(int argc, char* argv[])
{
const char* pCodecName = avcodec_get_name(AV_CODEC_ID_MP3);
printf("codec: %d -> %s \n", AV_CODEC_ID_MP3, pCodecName);
const AVCodec* pCodec = avcodec_find_decoder_by_name(pCodecName);
if (pCodec == NULL)
{
printf("can't find decoder! \n");
return -1;
}
// 根据指定解码器初使化对应的解码上下文
AVCodecContext* pCodecCTX = avcodec_alloc_context3(pCodec);
if (pCodecCTX == NULL)
{
printf("can't alloc decoder context! \n");
return -1;
}
// 打开解码器上下文
int rc = avcodec_open2(pCodecCTX, pCodec, NULL);
if (rc < 0)
{
char sError[128] = {0};
av_strerror(rc, sError, sizeof(sError));
printf("avcodec_open2() ret:[%d:%s] \n", rc, sError);
return -1;
}
// 打开裸流解析上下文
AVCodecParserContext* pCodecParserCTX = av_parser_init(pCodec->id);
if (pCodecParserCTX == NULL)
{
printf("init parser context failed! \n");
return -1;
}
AVPacket* pPacket = av_packet_alloc();
AVFrame* pFrame = av_frame_alloc();
FILE* pFileInput = fopen("test.mp3", "rb");
FILE* pFileOutput = fopen("test.pcm", "wb");
while (true)
{
const int BUFF_SIZE = 100; //20480;
char sDataBuffer[BUFF_SIZE + AV_INPUT_BUFFER_PADDING_SIZE] = {0};
int nBytesRead = fread(sDataBuffer, 1, BUFF_SIZE, pFileInput);
if (nBytesRead <= 0)
break;
printf("read bytes: %d \n", nBytesRead);
// 一次读取,全部喂给
int nOffset = 0;
while (nOffset < nBytesRead)
{
// 尽量喂给,但是一次最大只解析出一个包
int nPacketSize = av_parser_parse2(pCodecParserCTX, pCodecCTX, &(pPacket->data), &(pPacket->size),
(uint8_t*)sDataBuffer + nOffset, nBytesRead - nOffset, AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0);
printf("\t offset:%d max feed size:%d eat size:%d \n", nOffset, nBytesRead - nOffset, nPacketSize);
// 输出了报文,需要解码
if (pPacket->size > 0)
{
printf("\t\t out packet size:%d \n", pPacket->size);
// 解码,这里AVPacket结构只有data和size成员有效
if (!decode(pCodecCTX, pPacket, pFrame, pFileOutput))
{
printf("decode fatal! \n");
exit (-1);
}
}
nOffset += nPacketSize;
}
}
// 解码尾包
decode(pCodecCTX, NULL, pFrame, pFileOutput);
AVSampleFormat emSampleFormat = pCodecCTX->sample_fmt;
if (av_sample_fmt_is_planar(emSampleFormat))
{
const char* pPacked = av_get_sample_fmt_name(emSampleFormat);
printf("Warning: the sample format the decoder produced is planar(%s). This example will output the first channel only.\n",
pPacked ? pPacked : "?");
emSampleFormat = av_get_packed_sample_fmt(emSampleFormat);
}
printf("Play the output audio file with command: \n");
printf("\t ffplay -f %s -ac %d -ar %d test.pcm \n", getSampleFormatName(emSampleFormat), pCodecCTX->channels, pCodecCTX->sample_rate);
fclose(pFileOutput);
fclose(pFileInput);
av_packet_free(&pPacket);
av_frame_free(&pFrame);
av_parser_close(pCodecParserCTX);
avcodec_free_context(&pCodecCTX);
return 0;
}
const char* getSampleFormatName(enum AVSampleFormat emSampleFormat)
{
switch (emSampleFormat)
{
case AV_SAMPLE_FMT_U8:
return "u8";
case AV_SAMPLE_FMT_S16:
return AV_NE("s16be", "s16le");
case AV_SAMPLE_FMT_S32:
return AV_NE("s32be", "s32le");
case AV_SAMPLE_FMT_FLT:
return AV_NE("f32be", "f32le");
case AV_SAMPLE_FMT_DBL:
return AV_NE("f64be", "f64le");
}
return "unkown";
}
bool decode(AVCodecContext* pCodecCTX, const AVPacket* pPacket, AVFrame* pFrame, FILE* pFile)
{
// 发送数据
int rc = avcodec_send_packet(pCodecCTX, pPacket);
if (rc < 0)
{
char sError[128] = {0};
av_strerror(rc, sError, sizeof(sError));
printf("avcodec_send_packet() ret:[%d:%s] \n", rc, sError);
return false;
}
// 接收解码结果
while (true)
{
rc = avcodec_receive_frame(pCodecCTX, pFrame);
if (rc < 0)
{
// 无解码输出
if (rc == AVERROR(EAGAIN) || rc == AVERROR_EOF)
return true;
// 解码出错
char sError[128] = {0};
av_strerror(rc, sError, sizeof(sError));
printf("avcodec_receive_frame() ret:[%d:%s] \n", rc, sError);
return false;
}
printf("\t\t => frame format:[%d:%s] channels:[%d] sample_rate:[%d] nb_samples:[%d] pkt_size:[%d] linesize:[%d] \n",
pFrame->format, av_get_sample_fmt_name((AVSampleFormat)pFrame->format), pFrame->channels, pFrame->sample_rate, pFrame->nb_samples, pFrame->pkt_size, pFrame->linesize[0]);
int nSampleSize = av_get_bytes_per_sample(pCodecCTX->sample_fmt);
// 多通道按交错排列写入
for (int i = 0; i < pFrame->nb_samples; ++i)
{
for (int c = 0; c < pCodecCTX->channels; ++c)
{
fwrite(pFrame->data[c] + nSampleSize * i, 1, nSampleSize, pFile);
}
}
}
return true;
}
编译:
g++ -o decode_mp3 decode_mp3.cpp -I/usr/local/ffmpeg/include -L/usr/local/ffmpeg/lib -lavformat -lavcodec -lavutil
运行,输出如下:
$ ./decode_mp3
codec: 86017 -> mp3
read bytes: 100
offset:0 max feed size:100 eat size:100
read bytes: 100
offset:0 max feed size:100 eat size:100
read bytes: 100
offset:0 max feed size:100 eat size:100
read bytes: 100
offset:0 max feed size:100 eat size:100
read bytes: 100
offset:0 max feed size:100 eat size:17
out packet size:417
=> frame format:[6:s16p] channels:[2] sample_rate:[44100] nb_samples:[1152] pkt_size:[417] linesize:[2304]
offset:17 max feed size:83 eat size:83
read bytes: 100
offset:0 max feed size:100 eat size:100
read bytes: 100
offset:0 max feed size:100 eat size:100
read bytes: 100
offset:0 max feed size:100 eat size:100
read bytes: 100
offset:0 max feed size:100 eat size:35
out packet size:418
=> frame format:[6:s16p] channels:[2] sample_rate:[44100] nb_samples:[1152] pkt_size:[418] linesize:[2304]
offset:35 max feed size:65 eat size:65
......
read bytes: 100
offset:0 max feed size:100 eat size:100
read bytes: 100
offset:0 max feed size:100 eat size:100
read bytes: 100
offset:0 max feed size:100 eat size:100
read bytes: 96
offset:0 max feed size:96 eat size:96
out packet size:418
=> frame format:[6:s16p] channels:[2] sample_rate:[44100] nb_samples:[1152] pkt_size:[418] linesize:[2304]
Warning: the sample format the decoder produced is planar(s16p). This example will output the first channel only.
Play the output audio file with command:
ffplay -f s16le -ac 2 -ar 44100 test.pcm
这里为了演示读小包解析的过程,所以每次从文件中只读入100字节,交给解析器去拼接处理,正式场合中请尽量一次读多点数据,以减少系统调用。
由于是PCM文件,没有元数据说明,无法直接播放,使用下面的命令:
ffplay -f s16le -ac 2 -ar 44100 test.pcm