FFmpeg例子分析—decode_audio
FFmpeg流程
音频解码流程
音频解码详细步骤
代码
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <thread>
extern "C" {
#include "libavformat/avformat.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <libavutil/frame.h>
#include <libavutil/mem.h>
#include <libavcodec/avcodec.h>
}
using namespace std;
#pragma comment(lib,"avformat.lib")
#pragma comment(lib,"avutil.lib")
#pragma comment(lib,"avcodec.lib")
/**
* @file
* audio decoding with libavcodec API example
* libavcodec API音频解码示例
*
* @example decode_audio.c
*/
#define AUDIO_INBUF_SIZE 20480
#define AUDIO_REFILL_THRESH 4096
static void decode(AVCodecContext* dec_ctx, AVPacket* pkt, AVFrame* frame,
FILE* outfile)
{
int i, ch;
int ret, data_size;
/* send the packet with the compressed data to the decoder */
//将AVPacket压缩数据给解码器
ret = avcodec_send_packet(dec_ctx, pkt);
if (ret < 0) {
fprintf(stderr, "Error submitting the packet to the decoder\n");
exit(1);
}
/* read all the output frames (in general there may be any number of them */
while (ret >= 0) {
//获取到解码后的AVFrame数据
ret = avcodec_receive_frame(dec_ctx, frame);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
return;
else if (ret < 0) {
fprintf(stderr, "Error during decoding\n");
exit(1);
}
// 获取每个sample中的字节数
data_size = av_get_bytes_per_sample(dec_ctx->sample_fmt);
if (data_size < 0) {
/* This should not occur, checking just for paranoia */
fprintf(stderr, "Failed to calculate data size\n");
exit(1);
}
for (i = 0; i < frame->nb_samples; i++)
for (ch = 0; ch < dec_ctx->channels; ch++)
fwrite(frame->data[ch] + data_size * i, 1, data_size, outfile);
}
}
int main(int argc, char** argv)
{
const char* outfilename, * filename;
const AVCodec* codec;
AVCodecContext* c = NULL;
AVCodecParserContext* parser = NULL;
int len, ret;
FILE* f, * outfile;
uint8_t inbuf[AUDIO_INBUF_SIZE + AV_INPUT_BUFFER_PADDING_SIZE];
uint8_t* data;
size_t data_size;
AVPacket* pkt;
AVFrame* decoded_frame = NULL;
//if (argc <= 2) {
// fprintf(stderr, "Usage: %s <input file> <output file>\n", argv[0]);
// exit(0);
//}
//filename = argv[1];
//outfilename = argv[2];
filename = "C:\\Music\\yicijiuhao.mp3";
outfilename = "D:\\FFmpeg\\yicijiuhao.pcm";
pkt = av_packet_alloc();
/* find the MPEG audio decoder */
//根据指定的AVCodecID查找注册的解码器
codec = avcodec_find_decoder(AV_CODEC_ID_MP2);
if (!codec) {
fprintf(stderr, "Codec not found\n");
exit(1);
}
//初始化AVCodecParserContext
parser = av_parser_init(codec->id);
if (!parser) {
fprintf(stderr, "Parser not found\n");
exit(1);
}
//AVCodecContext* c = NULL;
//为AVCodecContext分配内存
c = avcodec_alloc_context3(codec);
if (!c) {
fprintf(stderr, "Could not allocate audio codec context\n");
exit(1);
}
/* open it */
//打开解码器
if (avcodec_open2(c, codec, NULL) < 0) {
fprintf(stderr, "Could not open codec\n");
exit(1);
}
f = fopen(filename, "rb");
if (!f) {
fprintf(stderr, "Could not open %s\n", filename);
exit(1);
}
outfile = fopen(outfilename, "wb");
if (!outfile) {
av_free(c);
exit(1);
}
/* decode until eof */
data = inbuf;
//AUDIO_INBUF_SIZE = 1024 * 20
data_size = fread(inbuf, 1, AUDIO_INBUF_SIZE, f);
while (data_size > 0) {
if (!decoded_frame) {
if (!(decoded_frame = av_frame_alloc())) {
fprintf(stderr, "Could not allocate audio frame\n");
exit(1);
}
}
//解析获得一个Packet
ret = av_parser_parse2(parser, c, &pkt->data, &pkt->size,
data, data_size,
AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0);
if (ret < 0) {
fprintf(stderr, "Error while parsing\n");
exit(1);
}
data += ret;
data_size -= ret;
if (pkt->size) {
//解码
//static void decode(AVCodecContext* dec_ctx, AVPacket* pkt, AVFrame* frame,FILE* outfile)
decode(c, pkt, decoded_frame, outfile);
}
if (data_size < AUDIO_REFILL_THRESH) {
memmove(inbuf, data, data_size);
data = inbuf;
len = fread(data + data_size, 1,
AUDIO_INBUF_SIZE - data_size, f);
if (len > 0)
data_size += len;
}
}
/* flush the decoder */
pkt->data = NULL;
pkt->size = 0;
decode(c, pkt, decoded_frame, outfile);
fclose(outfile);
fclose(f);
avcodec_free_context(&c);
av_parser_close(parser);
av_frame_free(&decoded_frame);
av_packet_free(&pkt);
return 0;
}
//参考资料
//https://www.jianshu.com/p/fb8ce81a7cf4
大概就是这样
音频重新填充阈值,代码分析
当inbuf里面的数据量小于阈值里,把剩下的数据移动到缓冲区前面,重置data指针。再从音视频文件中读取数据,放入缓冲区,增加data_size。
函数解读
fread函数
fread函数用于从文件流中读取数据,其函数原型为:
size_t fread(void* buffer, size_t size, size_t count, FILE*stream);
参数
参数 | 说明 |
buffer | 指向要读取的数组中首个对象的指针 |
size | 每个对象的大小(单位是字节) |
count | 要读取的对象个数 |
stream | 输入流 |
av_frame_alloc函数
Allocate an AVFrame and set its fields to default values. The resulting
struct must be freed using av_frame_free().
分配AVFrame并将其字段设置为默认值。必须使用av_frame_free释放生成的结构
@return An AVFrame filled with default values or NULL on failure.
AVFrame填充了默认值或失败时为NULL
@note this only allocates the AVFrame itself, not the data buffers. Those
must be allocated through other means, e.g. with av_frame_get_buffer() or
manually.
这只分配AVFrame本身,而不是数据缓冲区。必须通过其他方式分配
AVFrame结构体分析看这里。
av_parser_parse2函数—解析数据包
函数原型
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);
参数说明
Parse a packet.
@param s parser context.
@param avctx codec context.
@param poutbuf set to pointer to parsed buffer or NULL if not yet finished.
@param poutbuf_size set to size of parsed buffer or zero if not yet finished.
@param buf input buffer.
@param buf_size buffer size in bytes without the padding. I.e. the full buffer
size is assumed to be buf_size + AV_INPUT_BUFFER_PADDING_SIZE.
To signal EOF, this should be 0 (so that the last frame
can be output).
@param pts input presentation timestamp.
@param dts input decoding timestamp.
@param pos input byte position in stream.
@return the number of bytes of the input bitstream used.
使用方法
while(in_len){
len = av_parser_parse2(myparser, AVCodecContext, &data, &size,
in_data, in_len,
pts, dts, pos);
in_data += len;
in_len -= len;
if(size)
decode_frame(data, size);
}
函数代码
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)
{
int index, i;
uint8_t dummy_buf[AV_INPUT_BUFFER_PADDING_SIZE];
av_assert1(avctx->codec_id != AV_CODEC_ID_NONE);
/* Parsers only work for the specified codec ids. */
av_assert1(avctx->codec_id == s->parser->codec_ids[0] ||
avctx->codec_id == s->parser->codec_ids[1] ||
avctx->codec_id == s->parser->codec_ids[2] ||
avctx->codec_id == s->parser->codec_ids[3] ||
avctx->codec_id == s->parser->codec_ids[4]);
if (!(s->flags & PARSER_FLAG_FETCHED_OFFSET)) {
s->next_frame_offset =
s->cur_offset = pos;
s->flags |= PARSER_FLAG_FETCHED_OFFSET;
}
if (buf_size == 0) {
/* padding is always necessary even if EOF, so we add it here */
memset(dummy_buf, 0, sizeof(dummy_buf));
buf = dummy_buf;
} else if (s->cur_offset + buf_size != s->cur_frame_end[s->cur_frame_start_index]) { /* skip remainder packets */
/* add a new packet descriptor */
i = (s->cur_frame_start_index + 1) & (AV_PARSER_PTS_NB - 1);
s->cur_frame_start_index = i;
s->cur_frame_offset[i] = s->cur_offset;
s->cur_frame_end[i] = s->cur_offset + buf_size;
s->cur_frame_pts[i] = pts;
s->cur_frame_dts[i] = dts;
s->cur_frame_pos[i] = pos;
}
if (s->fetch_timestamp) {
s->fetch_timestamp = 0;
s->last_pts = s->pts;
s->last_dts = s->dts;
s->last_pos = s->pos;
ff_fetch_timestamp(s, 0, 0, 0);
}
/* WARNING: the returned index can be negative */
index = s->parser->parser_parse(s, avctx, (const uint8_t **) poutbuf,
poutbuf_size, buf, buf_size);
av_assert0(index > -0x20000000); // The API does not allow returning AVERROR codes
#define FILL(name) if(s->name > 0 && avctx->name <= 0) avctx->name = s->name
if (avctx->codec_type == AVMEDIA_TYPE_VIDEO) {
FILL(field_order);
}
/* update the file pointer */
if (*poutbuf_size) {
/* fill the data for the current frame */
s->frame_offset = s->next_frame_offset;
/* offset of the next frame */
s->next_frame_offset = s->cur_offset + index;
s->fetch_timestamp = 1;
}
if (index < 0)
index = 0;
s->cur_offset += index;
return index;
}
memmove函数
memmove用于拷贝字节,如果目标区域和源区域有重叠的话,memmove能够保证源串在被覆盖之前将重叠区域的字节拷贝到目标区域中,但复制后源内容会被更改。但是当目标区域与源区域没有重叠则和memcpy函数功能相同。
原型:
void *memmove( void* dest, const void* src, size_t count );
头文件:
<string.h>
功能:
由src所指内存区域复制count个字节到dest所指内存区域。
相关函数:
memset、memcpy
inbuf为什么要加上64
// 20480 + 64
uint8_t inbuf[AUDIO_INBUF_SIZE + AV_INPUT_BUFFER_PADDING_SIZE];
首先我们创建了一个AUDIO_INBUF_SIZE + AV_INPUT_BUFFER_PADDING_SIZE大小的数据缓存区,加上AV_INPUT_BUFFER_PADDING_SIZE是为了防止某些优化过的reader一次性读取过多导致越界。然后调用fread函数从本地文件中每次读取AUDIO_INBUF_SIZE大小的数据到缓存区中。
参考
ffmpeg_sample解读_decode_audio关于FFmpeg的AAC解码实战