av_parse_parse2
用于从输入的数据流中将流中的压缩编码数据一帧一帧的分离出来,也就是从一端数据码流中将需要解码的packet读取出来
由于H264由特殊的格式组成 [StartCode] [NALU Header] [NALU Payload] ,其中 Start Code ⽤于标示这是⼀个NALU 单元的开 始,必须是"00 00 00 01" 或"00 00 01",除此之外基本相当于⼀个NAL header + RBSP;其中NALU Hearder,,长度一个字节,后面5位代表了这一帧的具体属性,比如IDR,psp,sps等
核心函数是av_parser_parse2():
av_parser_parse2():从输入的数据流中解析数据获得一个Packet, 从输入的数据流中分离出一帧一帧的压缩编码数据。
这个函数的功能是从一大片数据中,分割出一个个nal单元,这也是为什么后面使用fread
读取文件流,使用av_parser_parse2就可以获取出packet
解码视频H264
//解码视频,使用了av_parser_parse2
/**
* @file
* video decoding with libavcodec API example
*
* @example decode_video.c
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <libavcodec/avcodec.h>
#define INBUF_SIZE 4096
static void pgm_save(unsigned char *buf, int wrap, int xsize, int ysize,
char *filename)
{
FILE *f;
int i;
f = fopen(filename,"wb");
fprintf(f, "P5\n%d %d\n%d\n", xsize, ysize, 255);
for (i = 0; i < ysize; i++)
fwrite(buf + i * wrap, 1, xsize, f);
fclose(f);
}
static void decode(AVCodecContext *dec_ctx, AVFrame *frame, AVPacket *pkt,
const char *filename)
{
char buf[1024];
int ret;
ret = avcodec_send_packet(dec_ctx, pkt);
if (ret < 0) {
fprintf(stderr, "Error sending a packet for decoding\n");
exit(1);
}
while (ret >= 0) {
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);
}
printf("saving frame %3d\n", dec_ctx->frame_number);
fflush(stdout);
/* the picture is allocated by the decoder. no need to
free it */
snprintf(buf, sizeof(buf), "%s-%d", filename, dec_ctx->frame_number);
pgm_save(frame->data[0], frame->linesize[0],
frame->width, frame->height, buf);
}
}
//从本地读取一个h264/265文件,但是一定要保证这个文件没有被封装过
//输入必须是只包含视频编码数据“裸流”(例如H.264、HEVC码流文件),而不能是包含封装格式的媒体数据(例如AVI、MKV、MP4)
int main(int argc, char **argv)
{
const char *filename, *outfilename;
const AVCodec *codec; // 编解码器
AVCodecParserContext *parser; // 码流解析器
AVCodecContext *c= NULL; // 编解码器上下文
FILE *f;
AVFrame *frame;
uint8_t inbuf[INBUF_SIZE + AV_INPUT_BUFFER_PADDING_SIZE];
uint8_t *data;
size_t data_size;
int ret;
AVPacket *pkt;
if (argc <= 2) {
fprintf(stderr, "Usage: %s <input file> <output file>\n"
"And check your input file is encoded by mpeg1video please.\n", argv[0]);
exit(0);
}
filename = argv[1];
outfilename = argv[2];
pkt = av_packet_alloc();
if (!pkt)
exit(1);
/* set end of buffer to 0 (this ensures that no overreading happens for damaged MPEG streams) */
memset(inbuf + INBUF_SIZE, 0, AV_INPUT_BUFFER_PADDING_SIZE);
/* find the MPEG-1 video decoder */
//查找解码器
//比如这里AVCodecContext和
codec = avcodec_find_decoder(AV_CODEC_ID_H264);
if (!codec) {
fprintf(stderr, "Codec not found\n");
exit(1);
}
//初始化解析器,也就是根据codec->id来确定解析器的具体类型,解析器具体类型可以看文章最后。
parser = av_parser_init(codec->id);
if (!parser) {
fprintf(stderr, "parser not found\n");
exit(1);
}
// 根据编解码器创建编解码器上下文,为什么创建上下文在之前的文章有介绍
c = avcodec_alloc_context3(codec);
if (!c) {
fprintf(stderr, "Could not allocate video codec context\n");
exit(1);
}
/* For some codecs, such as msmpeg4 and mpeg4, width and height
MUST be initialized there because this information is not
available in the bitstream. */
/* open it */
//打开解码器
//如果是cpu解码,可以设置解码线程数量
//分配AVCodecContext
/**
* thread count
* is used to decide how many independent tasks should be passed to execute()
* - encoding: Set by user.
* - decoding: Set by user.
*/
c->thread_count=10;
//打开解码器,并将解码器和上下文建立连接
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);
}
frame = av_frame_alloc();
if (!frame) {
fprintf(stderr, "Could not allocate video frame\n");
exit(1);
}
while (!feof(f)) {
/* read raw data from the input file */
data_size = fread(inbuf, 1, INBUF_SIZE, f);
if (!data_size)
break;
/* use the parser to split the data into frames */
data = inbuf;
//注意这里,这里是个小循环,也就是说,data中有可能包含多个nal单元,
//每次循环只会找出一个nal,循环,直到所有nal找出来为止
while (data_size > 0) {
//这里就是最核心的部分,从输入的码流中查找NALU,然后将读取的NALU数据放到pkt的data中,然后返回的pkt->size大于0,这说明成功分离除了一个NALU
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)
decode(c, frame, pkt, outfilename);
}
}
/* flush the decoder */
decode(c, frame, NULL, outfilename);
fclose(f);
av_parser_close(parser);
avcodec_free_context(&c);
av_frame_free(&frame);
av_packet_free(&pkt);
return 0;
}
解码音频AAC或MP3
/**
* @brief 解码音频,主要的测试格式aac和mp3
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <libavutil/frame.h>
#include <libavutil/mem.h>
#include <libavcodec/avcodec.h>
#define AUDIO_INBUF_SIZE 20480
#define AUDIO_REFILL_THRESH 4096
static char err_buf[128] = {0};
static char* av_get_err(int errnum)
{
av_strerror(errnum, err_buf, 128);
return err_buf;
}
static void print_sample_format(const AVFrame *frame)
{
printf("ar-samplerate: %uHz\n", frame->sample_rate); // 音频采样率
printf("ac-channel: %u\n", frame->channels); // 音频信道数目
printf("f-format: %u\n", frame->format);// 格式需要注意,实际存储到本地文件时已经改成交错模式
}
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 */
ret = avcodec_send_packet(dec_ctx, pkt); //向解码器发送packet,进行解码
if(ret == AVERROR(EAGAIN))
{
fprintf(stderr, "Receive_frame and send_packet both returned EAGAIN, which is an API violation.\n");
}
else if (ret < 0)
{
fprintf(stderr, "Error submitting the packet to the decoder, err:%s, pkt_size:%d\n",
av_get_err(ret), pkt->size);
// exit(1);
return;
}
/* read all the output frames (infile general there may be any number of them */
while (ret >= 0)
{
// 对于frame, avcodec_receive_frame内部每次都先调用
ret = avcodec_receive_frame(dec_ctx, frame); // 从解码器读取出已经解码的frame
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
return;
else if (ret < 0)
{
fprintf(stderr, "Error during decoding\n");
exit(1);
}
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);
}
static int s_print_format = 0;
if(s_print_format == 0)
{
s_print_format = 1;
print_sample_format(frame);
}
/**
P表示Planar(平面),其数据格式排列方式为 :
LLLLLLRRRRRRLLLLLLRRRRRRLLLLLLRRRRRRL...(每个LLLLLLRRRRRR为一个音频帧)
而不带P的数据格式(即交错排列)排列方式为:
LRLRLRLRLRLRLRLRLRLRLRLRLRLRLRLRLRLRL...(每个LR为一个音频样本)
播放范例: ffplay -ar 48000 -ac 2 -f f32le believe.pcm
*/
for (i = 0; i < frame->nb_samples; i++)
{
for (ch = 0; ch < dec_ctx->channels; ch++) // 交错的方式写入, 大部分float的格式输出
fwrite(frame->data[ch] + data_size*i, 1, data_size, outfile);
}
}
}
// 播放范例: ffplay -ar 48000 -ac 2 -f f32le believe.pcm
//从main函数看起,这个案例是从本地读取一个h264/265,aac或mp3文件,(当然也可以是其它类型文件)
//但是一定要保证这个文件没有被封装过
//输入必须是只包含视频编码数据“裸流”(例如H.264、HEVC码流aac,MP3文件),而不能是包含封装格式的媒体数据(例如AVI、MKV、MP4)
int main(int argc, char **argv)
{
const char *outfilename;
const char *filename;
const AVCodec *codec;
AVCodecContext *codec_ctx= NULL;
AVCodecParserContext *parser = NULL;
int len = 0;
int ret = 0;
FILE *infile = NULL;
FILE *outfile = NULL;
uint8_t inbuf[AUDIO_INBUF_SIZE + AV_INPUT_BUFFER_PADDING_SIZE];
uint8_t *data = NULL;
size_t data_size = 0;
AVPacket *pkt = NULL;
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];
pkt = av_packet_alloc();
enum AVCodecID audio_codec_id = AV_CODEC_ID_AAC;
if(strstr(filename, "aac") != NULL)
{
audio_codec_id = AV_CODEC_ID_AAC;
}
else if(strstr(filename, "mp3") != NULL)
{
audio_codec_id = AV_CODEC_ID_MP3;
}
else
{
printf("default codec id:%d\n", audio_codec_id);
}
//这里是寻找解码器,一般来说,ffmpeg都包含一个contex上下文,然后其中包含一个具体的业务指针
codec = avcodec_find_decoder(audio_codec_id); // AV_CODEC_ID_AAC或AV_CODEC_ID_MP3
if (!codec) {
fprintf(stderr, "Codec not found\n");
exit(1);
}
// 获取裸流的解析器 AVCodecParserContext(数据) + AVCodecParser(方法)
// 初始化解析器,也就是根据codec->id来确定解析器的具体类型
parser = av_parser_init(codec->id);
if (!parser) {
fprintf(stderr, "Parser not found\n");
exit(1);
}
// 分配解码器codec上下文: AVCodecContext* codec_ctx
codec_ctx = avcodec_alloc_context3(codec);
if (!codec_ctx) {
fprintf(stderr, "Could not allocate audio codec context\n");
exit(1);
}
// 将解码器和解码器上下文进行关联,打开解码器
if (avcodec_open2(codec_ctx, codec, NULL) < 0) {
fprintf(stderr, "Could not open codec\n");
exit(1);
}
// 打开输入文件
infile = fopen(filename, "rb");
if (!infile) {
fprintf(stderr, "Could not open %s\n", filename);
exit(1);
}
// 打开输出文件
outfile = fopen(outfilename, "wb");
if (!outfile) {
av_free(codec_ctx);
exit(1);
}
// 读取文件进行解码
data = inbuf;
data_size = fread(inbuf, 1, AUDIO_INBUF_SIZE, infile);
while (data_size > 0)
{
if (!decoded_frame)
{
if (!(decoded_frame = av_frame_alloc()))
{
fprintf(stderr, "Could not allocate audio frame\n");
exit(1);
}
}
ret = av_parser_parse2(parser, codec_ctx, &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)
decode(codec_ctx, pkt, decoded_frame, outfile);
if (data_size < AUDIO_REFILL_THRESH) // 如果数据少了则再次读取
{
memmove(inbuf, data, data_size); // 把之前剩的数据拷贝到buffer的起始位置
data = inbuf;
// 读取数据 长度: AUDIO_INBUF_SIZE - data_size
len = fread(data + data_size, 1, AUDIO_INBUF_SIZE - data_size, infile);
if (len > 0)
data_size += len;
}
}
/* 冲刷解码器 */
pkt->data = NULL; // 让其进入drain mode
pkt->size = 0;
decode(codec_ctx, pkt, decoded_frame, outfile);
fclose(outfile);
fclose(infile);
avcodec_free_context(&codec_ctx);
av_parser_close(parser);
av_frame_free(&decoded_frame);
av_packet_free(&pkt);
printf("main finish, please enter Enter and exit\n");
return 0;
}