一、了解FFmpeg
1.1 什么是FFmpeg
FFmpeg是一套可以用来记录、转换数字音频、视频,并能将其转化为流的开源计算机程序。采用LGPL或GPL许可证。它提供了录制、转换以及流化音视频的完整解决方案。它包含了非常先进的音频/视频编解码库libavcodec,为了保证高可移植性和编解码质量,libavcodec里很多code都是从头开发的。
FFmpeg在Linux平台下开发,但它同样也可以在其它操作系统环境中编译运行,包括Windows、Mac OS X等。这个项目最早由Fabrice Bellard发起,2004年至2015年间由Michael Niedermayer主要负责维护。许多FFmpeg的开发人员都来自MPlayer项目,而且当前FFmpeg也是放在MPlayer项目组的服务器上。项目的名称来自MPEG视频编码标准,前面的"FF"代表"Fast Forward"。FFmpeg编码库可以使用GPU加速。
1.2 FFmpeg的功能
1.2.1 视频采集功能
FFmpeg视频采集功能非常强大,不仅可以采集视频采集卡或USB摄像头的图像,还可以进行屏幕录制,同时还支持以RTP方式将视频流传送给支持RTSP的流媒体服务器,支持直播应用。
1.2.2 视频格式转换功能
FFmpeg视频转换功能。视频格式转换,比如可以将多种视频格式转换为flv格式,可不是视频信号转换 。
FFmpeg可以轻易地实现多种视频格式之间的相互转换(wma,rm,avi,mod等),例如可以将摄录下的视频avi等转成视频网站所采用的flv格式。
1.2.3 视频截图功能
对于选定的视频,截取指定时间的缩略图。视频抓图,获取静态图和动态图,不提倡抓gif文件;因为抓出的gif文件大而播放不流畅。
1.2.4 给视频加水印功能
使用FFmpeg视频添加水印(watermark)。
1.3 项目组成
FFmpeg是一套可以用来记录、转换数字音频、视频,并能将其转化为流的开源计算机程序。它包括了领先的音/视频编码库libavcodec等。
libavformat:用于各种音视频封装格式的生成和解析,包括获取解码所需信息以生成解码上下文结构和读取音视频帧等功能;
libavcodec:用于各种类型声音/图像编解码;
libavutil:包含一些公共的工具函数;
libswscale:用于视频场景比例缩放、色彩映射转换;
libpostproc:用于后期效果处理;
ffmpeg:该项目提供的一个工具,可用于格式转换、解码或电视卡即时编码等;
ffsever:一个 HTTP 多媒体即时广播串流服务器;
ffplay:是一个简单的播放器,使用ffmpeg 库解析和解码,通过SDL显示;
1.4 FFmpeg下载
Download FFmpegDownload FFmpegDownload FFmpeg
根据需求选择版本,免费开源。
二、学习笔记
2.1 视频文件解码流程
2.2 封装与编码格式
2.3 ffmpeg 视频解码 (解码为YUV)
2.3.1 方案流程图
注释:
1、av_register_all():注册所有组件。
2、avformat_open_input():打开输入视频文件。
3、avformat_find_stream_info():获取视频文件信息
4、avcodec_find_decoder():查找解码器。
5、avcodec_open2():打开解码器。
6、av_read_frame():从输入文件读取一帧压缩数据。
7、avcodec_decode_video2():解码一帧压缩数据。
8、avcodec_close():关闭解码器。
9、avformat_close_input():关闭输入视频文件。
2.3.2 视频解码程序
/**
* FFMPEG视频解码流程
* 1、av_register_all():注册所有组件。
* 2、avformat_open_input():打开输入视频文件。
* 3、avformat_find_stream_info():获取视频文件信息
* 4、avcodec_find_decoder():查找解码器。
* 5、avcodec_open2():打开解码器。
* 6、av_read_frame():从输入文件读取一帧压缩数据。
* 7、avcodec_decode_video2():解码一帧压缩数据。
* 8、avcodec_close():关闭解码器。
* 9、avformat_close_input():关闭输入视频文件。
*/
#include "stdafx.h"
#include <stdio.h>
#define __STDC_CONSTANT_MACROS
#ifdef _WIN32
//Windows
extern "C"
{
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libswscale/swscale.h"
#include "libavutil/imgutils.h"
};
#else
//Linux...
#ifdef __cplusplus
extern "C"
{
#endif
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
#include <libavutil/imgutils.h>
#ifdef __cplusplus
};
#endif
#endif
int main()
{
//文件格式上下文
AVFormatContext *pFormatCtx; // 封装格式上下文结构体,也是统领全局的结构体,保存了视频文件封装 格式相关信息。
int i = 0, videoindex;
AVCodecContext *pCodecCtx; // 编码器上下文结构体,保存了视频(音频)编解码相关信息。
AVCodec *pCodec; // AVCodec是存储编解码器信息的结构体。
AVFrame *pFrame, *pFrameYUV; // AVFrame是包含码流参数较多的结构体
unsigned char *out_buffer;
AVPacket *packet; // AVPacket是存储压缩编码数据相关信息的结构体
int y_size;
int ret, got_picture;
// struct SwsContext结构体位于libswscale类库中, 该类库主要用于处理图片像素数据, 可以完成图片像素格式的转换, 图片的拉伸等工作.
struct SwsContext *img_convert_ctx;
char filepath[] = "input.mkv";
FILE *fp_yuv = fopen("output.yuv", "wb+");
av_register_all(); // 注册所有组件
avformat_network_init(); // 对网络库进行全局初始化。
pFormatCtx = avformat_alloc_context(); // 初始化AVFormatContext结构体指针。使用avformat_free_context()释放内存。
if (avformat_open_input(&pFormatCtx, filepath, NULL, NULL) != 0) // 打开输入流并读取header。必须使用avformat_close_input()接口关闭。
{
printf("Couldn't open input stream.\n");
return -1;
}
//读取一部分视音频数据并且获得一些相关的信息
if (avformat_find_stream_info(pFormatCtx, NULL) < 0) // 读取媒体文件的包以获取流信息
{
printf("Couldn't find stream information.\n");
return -1;
}
//查找视频编码索引
videoindex = -1;
for (i = 0; i < pFormatCtx->nb_streams; i++)
{
if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO)
{
videoindex = i;
break;
}
}
if (videoindex == -1)
{
printf("Didn't find a video stream.\n");
return -1;
}
//编解码上下文
pCodecCtx = pFormatCtx->streams[videoindex]->codec;
//查找解码器
pCodec = avcodec_find_decoder(pCodecCtx->codec_id); // 查找符合ID的已注册解码器
if (pCodec == NULL)
{
printf("Codec not found.\n");
return -1;
}
//打开解码器
if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0)
{
printf("Could not open codec.\n");
return -1;
}
//申请AVFrame,用于原始视频
pFrame = av_frame_alloc();
//申请AVFrame,用于yuv视频
pFrameYUV = av_frame_alloc();
//分配内存,用于图像格式转换
out_buffer = (unsigned char *)av_malloc(av_image_get_buffer_size(AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height, 1));
// 根据指定的图像参数和提供的数组设置参数指针和linesize大小
av_image_fill_arrays(pFrameYUV->data, pFrameYUV->linesize, out_buffer,AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height, 1);
packet = (AVPacket *)av_malloc(sizeof(AVPacket));
//Output Info-----------------------------
printf("--------------- File Information ----------------\n");
//手工调试函数,输出tbn、tbc、tbr、PAR、DAR的含义
av_dump_format(pFormatCtx, 0, filepath, 0);
printf("-------------------------------------------------\n");
//申请转换上下文。 sws_getContext功能:初始化 SwsContext 结构体指针
img_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt,
pCodecCtx->width, pCodecCtx->height, AV_PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL);
//读取数据
while (av_read_frame(pFormatCtx, packet) >= 0) // 读取码流中的音频若干帧或者视频一帧
{
if (packet->stream_index == videoindex)
{
// avcodec_decode_video2 功能:解码一帧视频数据
ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, packet);
if (ret < 0)
{
printf("Decode Error.\n");
return -1;
}
if (got_picture >= 1)
{
//成功解码一帧
sws_scale(img_convert_ctx, (const unsigned char* const*)pFrame->data, pFrame->linesize, 0, pCodecCtx->height,
pFrameYUV->data, pFrameYUV->linesize); // 转换图像格式
y_size = pCodecCtx->width*pCodecCtx->height;
// fwrite 功能:把 pFrameYUV 所指向数据写入到 fp_yuv 中。
fwrite(pFrameYUV->data[0], 1, y_size, fp_yuv); //Y
fwrite(pFrameYUV->data[1], 1, y_size / 4, fp_yuv); //U
fwrite(pFrameYUV->data[2], 1, y_size / 4, fp_yuv); //V
printf("Succeed to decode 1 frame!\n");
}
else
{
//未解码到一帧,可能时结尾B帧或延迟帧,在后面做flush decoder处理
}
}
av_free_packet(packet); // free
}
//flush decoder
//FIX: Flush Frames remained in Codec
while (true)
{
if (!(pCodec->capabilities & CODEC_CAP_DELAY))
return 0;
// avcodec_decode_video2 功能:解码一帧视频数据
ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, packet);
if (ret < 0)
{
break;
}
if (!got_picture)
{
break;
}
sws_scale(img_convert_ctx, (const unsigned char* const*)pFrame->data, pFrame->linesize, 0, pCodecCtx->height,
pFrameYUV->data, pFrameYUV->linesize); // 转换图像格式
int y_size = pCodecCtx->width*pCodecCtx->height;
// fwrite 功能:把 pFrameYUV 所指向数据写入到 fp_yuv 中。
fwrite(pFrameYUV->data[0], 1, y_size, fp_yuv); //Y
fwrite(pFrameYUV->data[1], 1, y_size / 4, fp_yuv); //U
fwrite(pFrameYUV->data[2], 1, y_size / 4, fp_yuv); //V
printf("Flush Decoder: Succeed to decode 1 frame!\n");
}
sws_freeContext(img_convert_ctx);
av_frame_free(&pFrameYUV);
av_frame_free(&pFrame);
avcodec_close(pCodecCtx);
avformat_close_input(&pFormatCtx);
fclose(fp_yuv);
return 0;
}
2.3.3 注意事项
解码后数据需要经过处理,去除无效数据,如下:
用sws_scale() 进行转换。
2.4 ffmpeg 视频编码 (YUV编码为H.264)
2.4.1 方案流程图
注释:
1、av_register_all():注册FFmpeg所有编解码器。
2、avformat_alloc_output_context2():初始化输出码流的AVFormatContext。
3、avio_open():打开输出文件。
4、av_new_stream():创建输出码流的AVStream。
5、avcodec_find_encoder():查找编码器。
6、avcodec_open2():打开编码器。
7、avformat_write_header():写文件头(对于某些没有文件头的封装格式,不需要此函数。比如说MPEG2TS)。
8、不停地从码流中提取出YUV数据,进行编码。 avcodec_encode_video2():编码一帧视频。即将AVFrame(存储YUV像素数据)编码为AVPacket(存储H.264等格式的码流数据)。 av_write_frame():将编码后的视频码流写入文件。
9、flush_encoder():输入的像素数据读取完成后调用此函数。用于输出编码器中剩余的AVPacket。
10、av_write_trailer():写文件尾(对于某些没有文件头的封装格式,不需要此函数。比如说MPEG2TS)。
2.4.2 视频编码程序
/**
*************** FFMPEG视频编码流程 *******************
* 01、av_register_all():注册FFmpeg所有编解码器;
* 02、avformat_alloc_output_context2():初始化输出码流的AVFormatContext;
* 03、avio_open():打开输出文件;
* 04、av_new_stream():创建输出码流的AVStream;
* 05、avcodec_find_encoder():查找编码器;
* 06、avcodec_open2():打开编码器;
* 07、avformat_write_header():写文件头(对于某些没有文件头的封装格式,不需要此函数。比如说MPEG2TS);
* 08、不停地从码流中提取出YUV数据,进行编码;
* avcodec_encode_video2():编码一帧视频。即将AVFrame(存储YUV像素数据)编码为AVPacket(存储H.264等格式的码流数据);
* av_write_frame():将编码后的视频码流写入文件;
* 09、flush_encoder():输入的像素数据读取完成后调用此函数。用于输出编码器中剩余的AVPacket;
* 10、av_write_trailer():写文件尾(对于某些没有文件头的封装格式,不需要此函数。比如说MPEG2TS);
*/
#include <stdio.h>
#define __STDC_CONSTANT_MACROS
#ifdef _WIN32
//Windows
extern "C"
{
#include "libavutil/opt.h"
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
};
#else
//Linux...
#ifdef __cplusplus
extern "C"
{
#endif
#include <libavutil/opt.h>
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#ifdef __cplusplus
};
#endif
#endif
// 输入的像素数据读取完成后调用此函数,用于输出编码器中剩余的AVPacket
int flush_encoder(AVFormatContext *fmt_ctx,unsigned int stream_index){
int ret;
int got_frame;
AVPacket enc_pkt;
if (!(fmt_ctx->streams[stream_index]->codec->codec->capabilities & CODEC_CAP_DELAY))
return 0;
while (1) {
enc_pkt.data = NULL;
enc_pkt.size = 0;
av_init_packet(&enc_pkt);
//编码一帧视频。即将AVFrame(存储YUV像素数据)编码为AVPacket(存储H.264等格式的码流数据)。
ret = avcodec_encode_video2 (fmt_ctx->streams[stream_index]->codec, &enc_pkt,
NULL, &got_frame);
av_frame_free(NULL);
if (ret < 0)
break;
if (!got_frame){
ret=0;
break;
}
printf("Flush Encoder: Succeed to encode 1 frame!\tsize:%5d\n",enc_pkt.size);
/* mux encoded frame */
ret = av_write_frame(fmt_ctx, &enc_pkt);
if (ret < 0)
break;
}
return ret;
}
int main(int argc, char* argv[])
{
AVFormatContext* pFormatCtx; // 封装格式上下文结构体,也是统领全局的结构体,保存了视频文件封装 格式相关信息。
AVOutputFormat* fmt; // AVOutputFormat 结构体主要用于muxer,是音视频文件的一个封装器。
AVStream* video_st; // AVStream是存储每一个视频/音频流信息的结构体。
AVCodecContext* pCodecCtx; // 编码器上下文结构体,保存了视频(音频)编解码相关信息。
AVCodec* pCodec; // AVCodec是存储编解码器信息的结构体。
AVPacket pkt; // AVPacket是存储压缩编码数据相关信息的结构体
uint8_t* picture_buf;
AVFrame* pFrame; // AVFrame是包含码流参数较多的结构体
int picture_size;
int y_size;
int framecnt=0;
//FILE *in_file = fopen("src01_480x272.yuv", "rb"); // 输入原始YUV数据
FILE *in_file = fopen("../ds_480x272.yuv", "rb"); // 输入原始YUV数据
int in_w=480,in_h=272; // 输入数据的宽度和高度
int framenum=100; // 要编码的帧
//const char* out_file = "src01.h264"; // 输出文件路径
//const char* out_file = "src01.ts";
//const char* out_file = "src01.hevc";
const char* out_file = "ds.h264";
av_register_all(); // 注册ffmpeg所有编解码器
//方法1.
pFormatCtx = avformat_alloc_context(); // 初始化 pFormatCtx。 AVFormatContext 用 avformat_alloc_context() 进行初始化
//Guess Format
fmt = av_guess_format(NULL, out_file, NULL); // av_guess_format 这是一个决定视频输出时封装方式的函数,其中有三个参数,写任何一个参数,都会自动匹配相应的封装方式。
pFormatCtx->oformat = fmt;
//方法2.
//avformat_alloc_output_context2(&pFormatCtx, NULL, NULL, out_file); // 初始化输出码流的AVFormatContext
//fmt = pFormatCtx->oformat;
//Open output URL
if (avio_open(&pFormatCtx->pb,out_file, AVIO_FLAG_READ_WRITE) < 0){ // avio_open 打开输出文件
printf("Failed to open output file! \n");
return -1;
}
video_st = avformat_new_stream(pFormatCtx, 0); // 创建输出码流的AVStream
video_st->time_base.num = 1; // num 分子
video_st->time_base.den = 25; // den 分母
if (video_st==NULL){
return -1;
}
// 必须设置的参数
pCodecCtx = video_st->codec;
//pCodecCtx->codec_id =AV_CODEC_ID_HEVC;
pCodecCtx->codec_id = fmt->video_codec;
pCodecCtx->codec_type = AVMEDIA_TYPE_VIDEO;
pCodecCtx->pix_fmt = PIX_FMT_YUV420P;
pCodecCtx->width = in_w;
pCodecCtx->height = in_h;
pCodecCtx->time_base.num = 1;
pCodecCtx->time_base.den = 25;
pCodecCtx->bit_rate = 400000;
pCodecCtx->gop_size=250;
//H264
//pCodecCtx->me_range = 16;
//pCodecCtx->max_qdiff = 4;
//pCodecCtx->qcompress = 0.6;
pCodecCtx->qmin = 10;
pCodecCtx->qmax = 51;
// 可选参数
pCodecCtx->max_b_frames=3;
// 设置选项
AVDictionary *param = 0;
//H.264
if(pCodecCtx->codec_id == AV_CODEC_ID_H264) {
av_dict_set(¶m, "preset", "slow", 0);
av_dict_set(¶m, "tune", "zerolatency", 0);
//av_dict_set(¶m, "profile", "main", 0);
}
//H.265
if(pCodecCtx->codec_id == AV_CODEC_ID_H265){
av_dict_set(¶m, "preset", "ultrafast", 0);
av_dict_set(¶m, "tune", "zero-latency", 0);
}
//Show some Information
av_dump_format(pFormatCtx, 0, out_file, 1); // av_dump_format()是一个手工调试的函数,能使我们看到pFormatCtx->streams里面有什么内容。
pCodec = avcodec_find_encoder(pCodecCtx->codec_id); // 查找编码器
if (!pCodec){
printf("Can not find encoder! \n");
return -1;
}
if (avcodec_open2(pCodecCtx, pCodec,¶m) < 0){ // 打开编码器
printf("Failed to open encoder! \n");
return -1;
}
pFrame = av_frame_alloc(); // AVFrame结构,av_frame_alloc申请内存,av_frame_free释放内存
picture_size = avpicture_get_size(pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height); //计算这个格式的图片,需要多少字节来存储
picture_buf = (uint8_t *)av_malloc(picture_size);
// 这个函数是为已经分配的空间的结构体AVPicture挂上一段用于保存数据的空间
avpicture_fill((AVPicture *)pFrame, picture_buf, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height);
// 写文件头(对于某些没有文件头的封装格式,不需要此函数。比如说MPEG2TS)。
avformat_write_header(pFormatCtx,NULL);
av_new_packet(&pkt,picture_size); // 分配数据包的有效size并初始化
y_size = pCodecCtx->width * pCodecCtx->height;
// 一帧一帧循环操作
for (int i=0; i<framenum; i++){
// Read raw YUV data
if (fread(picture_buf, 1, y_size*3/2, in_file) <= 0){ // fread函数,从文件流中读取数据,如果不成功或读到文件末尾返回 0
printf("Failed to read raw data! \n");
return -1;
}else if(feof(in_file)){ // 判断文件是否结束
break;
}
pFrame->data[0] = picture_buf; // Y
pFrame->data[1] = picture_buf+ y_size; // U
pFrame->data[2] = picture_buf+ y_size*5/4; // V
// PTS
pFrame->pts=i; // pts : 以时间为基本单位的表示时间戳(应该向用户显示帧的时间)。
int got_picture=0;
// 编码一帧视频。即将AVFrame(存储YUV像素数据)编码为AVPacket(存储H.264等格式的码流数据)。
// 成功时返回0,失败时返回负错误代码 失败时返回错误返回码
int ret = avcodec_encode_video2(pCodecCtx, &pkt,pFrame, &got_picture);
if(ret < 0){
printf("Failed to encode! \n");
return -1;
}
if (got_picture==1){
printf("Succeed to encode frame: %5d\tsize:%5d\n",framecnt,pkt.size);
framecnt++;
pkt.stream_index = video_st->index;
ret = av_write_frame(pFormatCtx, &pkt); // 将编码后的视频码流写入文件,
av_free_packet(&pkt); // free
}
}
// Flush Encoder
int ret = flush_encoder(pFormatCtx,0); // 输入的像素数据读取完成后调用此函数,用于输出编码器中剩余的AVPacket
if (ret < 0) {
printf("Flushing encoder failed\n");
return -1;
}
// 写文件尾(对于某些没有文件头的封装格式,不需要此函数。比如说MPEG2TS)
av_write_trailer(pFormatCtx);
// Clean
if (video_st){
avcodec_close(video_st->codec);
av_free(pFrame);
av_free(picture_buf);
}
avio_close(pFormatCtx->pb);
avformat_free_context(pFormatCtx);
fclose(in_file);
return 0;
}
2.5 ffmpeg 音频编码 (PCM编码为AAC)
2.5.1 方案流程图
注释:
1、av_register_all():注册FFmpeg所有编解码器。
2、avformat_alloc_output_context2():初始化输出码流的AVFormatContext。
3、avio_open():打开输出文件。
4、av_new_stream():创建输出码流的AVStream。
5、avcodec_find_encoder():查找编码器。
6、avcodec_open2():打开编码器。
7、avformat_write_header():写文件头(对于某些没有文件头的封装格式,不需要此函数。比 如说MPEG2TS)。
8、avcodec_encode_audio2():编码音频。即将AVFrame(存储PCM采样数据)编码为AVPacket(存储AAC,MP3等格式的码流数据)。
9、av_write_frame():将编码后的视频码流写入文件。
10、av_write_trailer():写文件尾(对于某些没有文件头的封装格式,不需要此函数。比如说MPEG2TS)。
2.5.2 音频编码程序
#include <stdio.h>
#include "audio_encoder.h"
#define __STDC_CONSTANT_MACROS
#ifdef _WIN32
//Windows
extern "C"
{
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
};
#else
//Linux...
#ifdef __cplusplus
extern "C"
{
#endif
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#ifdef __cplusplus
};
#endif
#endif
int flush_encoder(AVFormatContext *fmt_ctx,unsigned int stream_index){
int ret;
int got_frame;
AVPacket enc_pkt;
if (!(fmt_ctx->streams[stream_index]->codec->codec->capabilities &
CODEC_CAP_DELAY))
return 0;
while (1) {
enc_pkt.data = NULL;
enc_pkt.size = 0;
av_init_packet(&enc_pkt);
ret = avcodec_encode_audio2 (fmt_ctx->streams[stream_index]->codec, &enc_pkt,
NULL, &got_frame);
av_frame_free(NULL);
if (ret < 0)
break;
if (!got_frame){
ret=0;
break;
}
printf("Flush Encoder: Succeed to encode 1 frame!\tsize:%5d\n",enc_pkt.size);
/* mux encoded frame */
ret = av_write_frame(fmt_ctx, &enc_pkt);
if (ret < 0)
break;
}
return ret;
}
int main(int argc, char* argv[])
{
AudioEncoder audioEncoder("tdjm.pcm","tdjm.aac");
audioEncoder.encode_now();
AVFormatContext* pFormatCtx; // 封装格式上下文结构体,也是统领全局的结构体,保存了视频文件封装 格式相关信息。
AVOutputFormat* fmt; // AVOutputFormat 结构体主要用于muxer,是音视频文件的一个封装器。
AVStream* audio_st; // AVStream是存储每一个视频/音频流信息的结构体。
AVCodecContext* pCodecCtx; // 编码器上下文结构体,保存了视频(音频)编解码相关信息。
AVCodec* pCodec; // AVCodec是存储编解码器信息的结构体。
uint8_t* frame_buf;
AVFrame* pFrame; // AVFrame是包含码流参数较多的结构体
AVPacket pkt; // AVPacket是存储压缩编码数据相关信息的结构体
int got_frame=0;
int ret=0;
int size=0;
FILE *in_file=NULL; //Raw PCM data
int framenum=1000; //Audio frame number
const char* out_file = "tdjm.aac"; //Output URL
int i;
in_file= fopen("tdjm.pcm", "rb");
av_register_all(); // 注册ffmpeg所有编解码器
//Method 1.
pFormatCtx = avformat_alloc_context(); // 初始化 pFormatCtx。 AVFormatContext 用 avformat_alloc_context() 进行初始化
fmt = av_guess_format(NULL, out_file, NULL); // av_guess_format 这是一个决定视频输出时封装方式的函数,其中有三个参数,写任何一个参数,都会自动匹配相应的封装方式。
pFormatCtx->oformat = fmt;
//Method 2.
//avformat_alloc_output_context2(&pFormatCtx, NULL, NULL, out_file);
//fmt = pFormatCtx->oformat;
//Open output URL
if (avio_open(&pFormatCtx->pb,out_file, AVIO_FLAG_READ_WRITE) < 0){ // avio_open 打开输出文件
printf("Failed to open output file!\n");
return -1;
}
audio_st = avformat_new_stream(pFormatCtx, 0); // 创建输出码流的AVStream
if (audio_st==NULL){
return -1;
}
// 必须设置的参数
pCodecCtx = audio_st->codec;
pCodecCtx->codec_id = fmt->audio_codec;
pCodecCtx->codec_type = AVMEDIA_TYPE_AUDIO;
pCodecCtx->sample_fmt = AV_SAMPLE_FMT_S16;
pCodecCtx->sample_rate= 44100;
pCodecCtx->channel_layout=AV_CH_LAYOUT_STEREO;
pCodecCtx->channels = av_get_channel_layout_nb_channels(pCodecCtx->channel_layout);
pCodecCtx->bit_rate = 64000;
// av_dump_format()是一个手工调试的函数,能使我们看到pFormatCtx->streams里面有什么内容。
av_dump_format(pFormatCtx, 0, out_file, 1);
// 查找编码器
pCodec = avcodec_find_encoder(pCodecCtx->codec_id);
if (!pCodec){
printf("Can not find encoder!\n");
return -1;
}
// 打开编码器
if (avcodec_open2(pCodecCtx, pCodec,NULL) < 0){
printf("Failed to open encoder!\n");
return -1;
}
pFrame = av_frame_alloc(); // AVFrame结构,av_frame_alloc申请内存,av_frame_free释放内存
pFrame->nb_samples= pCodecCtx->frame_size; // 此帧描述的音频采样数(每个通道)
pFrame->format= pCodecCtx->sample_fmt; // 帧的格式,如果未知或未设置,则为-1
// 获取给定音频参数所需的缓冲区大小。
size = av_samples_get_buffer_size(NULL, pCodecCtx->channels,pCodecCtx->frame_size,pCodecCtx->sample_fmt, 1);
frame_buf = (uint8_t *)av_malloc(size);
// 填充AVFrame音频数据和linesize指针。
avcodec_fill_audio_frame(pFrame, pCodecCtx->channels, pCodecCtx->sample_fmt,(const uint8_t*)frame_buf, size, 1);
// 写文件头
avformat_write_header(pFormatCtx,NULL);
av_new_packet(&pkt,size); // 分配有效size并初始化
for (i=0; i<framenum; i++){
//读取 PCM 数据
if (fread(frame_buf, 1, size, in_file) <= 0){
printf("Failed to read raw data! \n");
return -1;
}else if(feof(in_file)){
break;
}
pFrame->data[0] = frame_buf; //PCM Data
pFrame->pts=i*100;
got_frame=0;
// 编码音频。即将AVFrame(存储PCM采样数据)编码为AVPacket(存储AAC,MP3等格式的码流数据)。
ret = avcodec_encode_audio2(pCodecCtx, &pkt,pFrame, &got_frame);
if(ret < 0){
printf("Failed to encode!\n");
return -1;
}
if (got_frame==1){
printf("Succeed to encode 1 frame! \tsize:%5d\n",pkt.size);
pkt.stream_index = audio_st->index;
ret = av_write_frame(pFormatCtx, &pkt); // 将编码后的音频数据写入文件
av_free_packet(&pkt);
}
}
// 用于输出编码器中剩余的AVPacket
ret = flush_encoder(pFormatCtx,0);
if (ret < 0) {
printf("Flushing encoder failed\n");
return -1;
}
// 写文件尾
av_write_trailer(pFormatCtx);
// Clean
if (audio_st){
avcodec_close(audio_st->codec);
av_free(pFrame);
av_free(frame_buf);
}
avio_close(pFormatCtx->pb);
avformat_free_context(pFormatCtx);
fclose(in_file);
return 0;
}
2.6 ffmpeg 音频解码
2.6.1 方案一 (存为pcm)
#ifdef __cplusplus
extern "C"
{
#endif
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libavdevice/avdevice.h"
#include "libavfilter/avfilter.h"
#include "libavfilter/avfiltergraph.h"
#include "libavfilter/buffersink.h"
#include "libavfilter/buffersrc.h"
#include "libavutil/audio_fifo.h"
#include "libavutil/avutil.h"
#include "libavutil/fifo.h"
#ifdef __cplusplus
}
#endif
#pragma comment(lib, "avcodec.lib")
#pragma comment(lib, "avformat.lib")
#pragma comment(lib, "avutil.lib")
#pragma comment(lib, "avdevice.lib")
#pragma comment(lib, "avfilter.lib")
//#pragma comment(lib, "avfilter.lib")
//#pragma comment(lib, "postproc.lib")
//#pragma comment(lib, "swresample.lib")
#pragma comment(lib, "swscale.lib")
#include <windows.h>
#include <conio.h>
#include <time.h>
#include <tchar.h>
AVFormatContext *ifmt_ctx = NULL;
int g_AudioStreamIndex = -1;
#include <stdio.h>
int openinputfile(const char* filename)
{
int ret = 0;
//open the input
if ((ret = avformat_open_input(&ifmt_ctx, filename, NULL, NULL)) < 0)
{
printf("can not open input");
return ret;
}
if ((ret = avformat_find_stream_info(ifmt_ctx, NULL)))
{
printf("can not find input stream info");
return ret;
}
//open the decoder
for (int i = 0; i < ifmt_ctx->nb_streams; i++)
{
if (ifmt_ctx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO)
{
g_AudioStreamIndex = i;
ret = avcodec_open2(ifmt_ctx->streams[i]->codec,
avcodec_find_decoder(ifmt_ctx->streams[i]->codec->codec_id), NULL);
if (ret < 0)
{
printf("can not open decoder");
return ret;
}
}
}
return 0;
}
int _tmain(int argc, _TCHAR* argv[])
{
if (argc < 2)
{
return -1;
}
AVPacket pkt_in, pkt_out;
AVFrame *frame = NULL;
unsigned int stream_index;
av_register_all();
if (openinputfile(argv[1]) < 0)
{
printf("failed to open input file");
goto end;
}
FILE *p = NULL;
char tmpName[100];
sprintf_s(tmpName, "%s_%d_%dchannel.pcm", argv[1],
ifmt_ctx->streams[g_AudioStreamIndex]->codec->sample_rate, ifmt_ctx->streams[g_AudioStreamIndex]->codec->channels);
p = fopen(tmpName, "w+b");
int size = av_get_bytes_per_sample(ifmt_ctx->streams[g_AudioStreamIndex]->codec->sample_fmt);
while(1)
{
if (av_read_frame(ifmt_ctx, &pkt_in) < 0)
{
break;
}
pkt_out.data = NULL;
pkt_out.size = 0;
av_init_packet(&pkt_out);
if (g_AudioStreamIndex == pkt_in.stream_index)
{
stream_index = pkt_in.stream_index;
frame = av_frame_alloc();
int got_frame = -1;
int ret = -1;
ret = avcodec_decode_audio4(ifmt_ctx->streams[stream_index]->codec, frame, &got_frame, &pkt_in);
if (ret < 0)
{
av_frame_free(&frame);
printf("decoding audio stream failed\n");
break;
}
if (got_frame)
{
if (frame->data[0] && frame->data[1])
{
for (int i = 0; i < ifmt_ctx->streams[stream_index]->codec->frame_size; i++)
{
fwrite(frame->data[0] + i * size, 1, size, p);
fwrite(frame->data[1] + i * size, 1, size, p);
}
}
else if(frame->data[0])
{
fwrite(frame->data[0], 1, frame->linesize[0], p);
}
}
}
}
fclose(p);
end:
avformat_close_input(&ifmt_ctx);
printf("enter any key to stop\n");
getchar();
return 0;
}
2.6.2 方案二 (aac解码pcm)
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define __STDC_CONSTANT_MACROS
extern "C" {
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavdevice/avdevice.h>
#include<libswresample/swresample.h>
}
#define MAX_AUDIO_FRAME_SIZE 192000
#define SAMPLE_PRT(fmt...) \
do {\
printf("[%s]-%d: ", __FUNCTION__, __LINE__);\
printf(fmt);\
}while(0)
const char *in_file = "./hefang.aac";
const char *out_file = "./hefang.pcm";
int main()
{
//注册所有的工具
av_register_all();
AVFormatContext *fmt_ctx = NULL;
AVCodecContext *cod_ctx = NULL;
AVCodec *cod = NULL;
//分配一个avformat
fmt_ctx = avformat_alloc_context();
if (fmt_ctx == NULL)
printf("alloc fail");
//打开文件,解封装
if (avformat_open_input(&fmt_ctx, in_file, NULL, NULL) != 0)
printf("open fail");
//查找文件的相关流信息
if (avformat_find_stream_info(fmt_ctx, NULL) < 0)
printf("find stream fail");
//输出格式信息
av_dump_format(fmt_ctx, 0, in_file, 0);
//查找解码信息
int stream_index = -1;
for (int i = 0; i < fmt_ctx->nb_streams; i++)
if (fmt_ctx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO) {
stream_index = i;
break;
}
if (stream_index == -1)
printf("find stream fail");
//保存解码器
cod_ctx = fmt_ctx->streams[stream_index]->codec;
cod = avcodec_find_decoder(cod_ctx->codec_id);
if (cod == NULL)
printf("find codec fail");
if (avcodec_open2(cod_ctx, cod, NULL) < 0)
printf("can't open codec");
FILE *out_fb = NULL;
out_fb = fopen(out_file, "wb");
//创建packet,用于存储解码前的数据
AVPacket *packet = (AVPacket *)malloc(sizeof(AVPacket));
av_init_packet(packet);
//设置转码后输出相关参数
//采样的布局方式
uint64_t out_channel_layout = AV_CH_LAYOUT_MONO;
//采样个数
int out_nb_samples = 1024;
//采样格式
enum AVSampleFormat sample_fmt = AV_SAMPLE_FMT_S16;
//采样率
int out_sample_rate = 44100;
//通道数
int out_channels = av_get_channel_layout_nb_channels(out_channel_layout);
printf("%d\n",out_channels);
//创建buffer
int buffer_size = av_samples_get_buffer_size(NULL, out_channels, out_nb_samples, sample_fmt, 1);
//注意要用av_malloc
uint8_t *buffer = (uint8_t *)av_malloc(MAX_AUDIO_FRAME_SIZE * 2);
//创建Frame,用于存储解码后的数据
AVFrame *frame = av_frame_alloc();
int got_picture;
int64_t in_channel_layout = av_get_default_channel_layout(cod_ctx->channels);
//打开转码器
struct SwrContext *convert_ctx = swr_alloc();
//设置转码参数
convert_ctx = swr_alloc_set_opts(convert_ctx, out_channel_layout, sample_fmt, out_sample_rate, \
in_channel_layout, cod_ctx->sample_fmt, cod_ctx->sample_rate, 0, NULL);
//初始化转码器
swr_init(convert_ctx);
//while循环,每次读取一帧,并转码
while (av_read_frame(fmt_ctx, packet) >= 0) {
if (packet->stream_index == stream_index) {
//解码声音
if (avcodec_decode_audio4(cod_ctx, frame, &got_picture, packet) < 0) {
printf("decode error");
return -1;
}
if (got_picture > 0) {
//转码
swr_convert(convert_ctx, &buffer, MAX_AUDIO_FRAME_SIZE, (const uint8_t **)frame->data, frame->nb_samples);
printf("pts:%10lld\t packet size:%d\n", packet->pts, packet->size);
fwrite(buffer, 1, buffer_size, out_fb);
}
got_picture=0;
}
av_free_packet(packet);
}
swr_free(&convert_ctx);
fclose(out_fb);
return 0;
}
2.7 ffmpeg 转码 (FLV 转码为AVI)
2.7.1 转码理论流程
2.7.2 转码流程图
注释:
1、open_input_file():打开输入文件,并初始化相关的结构体。
2、open_output_file():打开输出文件,并初始化相关的结构体。
3、init_filters():初始化AVFilter相关的结构体。
4、av_read_frame():从输入文件中读取一个AVPacket。
5、avcodec_decode_video2():解码一个视频AVPacket(存储H.264等压缩码流数据)为AVFrame(存储YUV等非压缩的像素数据)。
6、avcodec_decode_video4():解码一个音频AVPacket(存储MP3等压缩码流数据)为AVFrame(存储PCM采样数据)。
7、filter_encode_write_frame():编码一个AVFrame。
8、flush_encoder():输入文件读取完毕后,输出编码器中剩余的AVPacket。
2.7.3 转码代码
#include "stdafx.h"
extern "C"
{
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libavfilter/avfiltergraph.h"
#include "libavfilter/avcodec.h"
#include "libavfilter/buffersink.h"
#include "libavfilter/buffersrc.h"
#include "libavutil/avutil.h"
#include "libavutil/opt.h"
#include "libavutil/pixdesc.h"
};
static AVFormatContext *ifmt_ctx;
static AVFormatContext *ofmt_ctx;
typedef struct FilteringContext {
AVFilterContext *buffersink_ctx;
AVFilterContext *buffersrc_ctx;
AVFilterGraph *filter_graph;
} FilteringContext;
static FilteringContext *filter_ctx;
// 打开输入文件
static int open_input_file(const char *filename)
{
int ret;
unsigned int i;
ifmt_ctx = NULL;
// 打开多媒体数据并且获得一些相关的信息
if ((ret = avformat_open_input(&ifmt_ctx, filename, NULL, NULL)) < 0) {
av_log(NULL, AV_LOG_ERROR, "Cannot open input file\n");
return ret;
}
// 获取视频流信息
if ((ret = avformat_find_stream_info(ifmt_ctx, NULL)) < 0) {
av_log(NULL, AV_LOG_ERROR, "Cannot find stream information\n");
return ret;
}
for (i = 0; i < ifmt_ctx->nb_streams; i++) {
AVStream *stream; // AVStream是存储每一个视频/音频流信息的结构体。
AVCodecContext *codec_ctx; // 编码器上下文结构体,保存了视频(音频)编解码相关信息。
stream = ifmt_ctx->streams[i];
codec_ctx = stream->codec;
// 重新编码视频、音频和字幕等
if (codec_ctx->codec_type == AVMEDIA_TYPE_VIDEO
|| codec_ctx->codec_type == AVMEDIA_TYPE_AUDIO) {
/* Open decoder */
ret = avcodec_open2(codec_ctx,
avcodec_find_decoder(codec_ctx->codec_id), NULL); // avcodec_open2 该函数用于初始化一个视音频编解码器的AVCodecContext
if (ret < 0) {
av_log(NULL, AV_LOG_ERROR, "Failed to open decoder for stream #%u\n", i);
return ret;
}
}
}
// av_dump_format()是一个手工调试的函数,能使我们看到pFormatCtx->streams里面有什么内容。
av_dump_format(ifmt_ctx, 0, filename, 0);
return 0;
}
// 打开输出文件
static int open_output_file(const char *filename)
{
AVStream *out_stream; // AVStream是存储每一个视频/音频流信息的结构体。
AVStream *in_stream;
AVCodecContext *dec_ctx, *enc_ctx; // 编码器上下文结构体,保存了视频(音频)编解码相关信息。
AVCodec *encoder; // AVCodec是存储编解码器信息的结构体。
int ret;
unsigned int i;
ofmt_ctx = NULL;
// avformat_alloc_output_context2 负责分配输出 AVFormatContext。
// ffmpeg有各种各样的 Context ,其功能是管理各种各样的模块。
// 例如有一个输出文件:test.mp4,使用 avformat_alloc_output_context2 函数就可以根据文件名分配合适的 AVFormatContext 管理结构。
avformat_alloc_output_context2(&ofmt_ctx, NULL, NULL, filename);
if (!ofmt_ctx) {
av_log(NULL, AV_LOG_ERROR, "Could not create output context\n"); // 无法创建输出上下文
return AVERROR_UNKNOWN;
}
for (i = 0; i < ifmt_ctx->nb_streams; i++) {
out_stream = avformat_new_stream(ofmt_ctx, NULL); // 创建输出码流的AVStream
if (!out_stream) {
av_log(NULL, AV_LOG_ERROR, "Failed allocating output stream\n"); // 分配输出流失败
return AVERROR_UNKNOWN;
}
in_stream = ifmt_ctx->streams[i];
dec_ctx = in_stream->codec;
enc_ctx = out_stream->codec;
if (dec_ctx->codec_type == AVMEDIA_TYPE_VIDEO
|| dec_ctx->codec_type == AVMEDIA_TYPE_AUDIO) {
//在这个例子中,我们选择转码到同一个编解码器
encoder = avcodec_find_encoder(dec_ctx->codec_id); // 查找编码器
// 在本例中,我们将代码转换为相同的属性(图片大小, 采样率等)。
// 可以为输出更改这些属性使用过滤器轻松地进行流式处理
if (dec_ctx->codec_type == AVMEDIA_TYPE_VIDEO) { // 解码类型为视频解码
enc_ctx->height = dec_ctx->height; // 高
enc_ctx->width = dec_ctx->width; // 宽
enc_ctx->sample_aspect_ratio = dec_ctx->sample_aspect_ratio; // 长宽比
// 从支持的格式列表中获取第一种格式
enc_ctx->pix_fmt = encoder->pix_fmts[0];
// 视频时间可以设置为任何方便和编码器支持
enc_ctx->time_base = dec_ctx->time_base;
} else { // 音频解码
enc_ctx->sample_rate = dec_ctx->sample_rate; // 每秒采样数
enc_ctx->channel_layout = dec_ctx->channel_layout; // 音频通道布局。
enc_ctx->channels = av_get_channel_layout_nb_channels(enc_ctx->channel_layout); // 音频通道数
// 从支持的格式列表中获取第一种格式
enc_ctx->sample_fmt = encoder->sample_fmts[0];
AVRational time_base={1, enc_ctx->sample_rate};
enc_ctx->time_base = time_base;
}
// 第三个参数可用于将设置传递给编码器
ret = avcodec_open2(enc_ctx, encoder, NULL); // 打开编码器
if (ret < 0) {
av_log(NULL, AV_LOG_ERROR, "Cannot open video encoder for stream #%u\n", i); // 无法打开流的视频编码器
return ret;
}
} else if (dec_ctx->codec_type == AVMEDIA_TYPE_UNKNOWN) {
av_log(NULL, AV_LOG_FATAL, "Elementary stream #%d is of unknown type, cannot proceed\n", i); // 基本流的类型未知,无法继续
return AVERROR_INVALIDDATA;
} else {
// 如果这个流必须被重新计算
ret = avcodec_copy_context(ofmt_ctx->streams[i]->codec,
ifmt_ctx->streams[i]->codec); // avcodec_copy_context :编码参数上下文的拷贝
if (ret < 0) {
av_log(NULL, AV_LOG_ERROR, "Copying stream context failed\n"); // 拷贝流上下文失败
return ret;
}
}
if (ofmt_ctx->oformat->flags & AVFMT_GLOBALHEADER)
enc_ctx->flags |= CODEC_FLAG_GLOBAL_HEADER;
}
// av_dump_format()是一个手工调试的函数,能使我们看到pFormatCtx->streams里面有什么内容。
av_dump_format(ofmt_ctx, 0, filename, 1);
if (!(ofmt_ctx->oformat->flags & AVFMT_NOFILE)) {
ret = avio_open(&ofmt_ctx->pb, filename, AVIO_FLAG_WRITE); // 该函数用于打开FFmpeg输出文件
if (ret < 0) {
av_log(NULL, AV_LOG_ERROR, "Could not open output file '%s'", filename); // 无法打开输出文件
return ret;
}
}
// 初始化muxer,写入输出文件头
ret = avformat_write_header(ofmt_ctx, NULL);
if (ret < 0) {
av_log(NULL, AV_LOG_ERROR, "Error occurred when opening output file\n"); // 打开输出文件时出错
return ret;
}
return 0;
}
// 初始化AVFilter相关的结构体。
static int init_filter(FilteringContext* fctx, AVCodecContext *dec_ctx,
AVCodecContext *enc_ctx, const char *filter_spec)
{
char args[512];
int ret = 0;
AVFilter *buffersrc = NULL;
AVFilter *buffersink = NULL;
AVFilterContext *buffersrc_ctx = NULL;
AVFilterContext *buffersink_ctx = NULL;
AVFilterInOut *outputs = avfilter_inout_alloc();
AVFilterInOut *inputs = avfilter_inout_alloc();
AVFilterGraph *filter_graph = avfilter_graph_alloc();
if (!outputs || !inputs || !filter_graph) {
ret = AVERROR(ENOMEM);
goto end;
}
if (dec_ctx->codec_type == AVMEDIA_TYPE_VIDEO) { // 视频
buffersrc = avfilter_get_by_name("buffer");
buffersink = avfilter_get_by_name("buffersink");
if (!buffersrc || !buffersink) {
av_log(NULL, AV_LOG_ERROR, "filtering source or sink element not found\n"); // 未找到筛选源或基本元素
ret = AVERROR_UNKNOWN;
goto end;
}
_snprintf(args, sizeof(args),
"video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d",
dec_ctx->width, dec_ctx->height, dec_ctx->pix_fmt,
dec_ctx->time_base.num, dec_ctx->time_base.den,
dec_ctx->sample_aspect_ratio.num,
dec_ctx->sample_aspect_ratio.den);
// 创建过滤器实例并将其添加到现有的图形中
ret = avfilter_graph_create_filter(&buffersrc_ctx, buffersrc, "in",
args, NULL, filter_graph);
if (ret < 0) {
av_log(NULL, AV_LOG_ERROR, "Cannot create buffer source\n"); // 无法创建缓冲区源
goto end;
}
// 创建过滤器实例并将其添加到现有的图形中
ret = avfilter_graph_create_filter(&buffersink_ctx, buffersink, "out",
NULL, NULL, filter_graph);
if (ret < 0) {
av_log(NULL, AV_LOG_ERROR, "Cannot create buffer sink\n"); // 无法创建缓冲区接收器
goto end;
}
// 用来设置AVOption
ret = av_opt_set_bin(buffersink_ctx, "pix_fmts",
(uint8_t*)&enc_ctx->pix_fmt, sizeof(enc_ctx->pix_fmt),
AV_OPT_SEARCH_CHILDREN);
if (ret < 0) {
av_log(NULL, AV_LOG_ERROR, "Cannot set output pixel format\n"); // 无法设置输出像素格式
goto end;
}
} else if (dec_ctx->codec_type == AVMEDIA_TYPE_AUDIO) { // 音频
buffersrc = avfilter_get_by_name("abuffer");
buffersink = avfilter_get_by_name("abuffersink");
if (!buffersrc || !buffersink) {
av_log(NULL, AV_LOG_ERROR, "filtering source or sink element not found\n"); // 未找到筛选源或基本元素
ret = AVERROR_UNKNOWN;
goto end;
}
if (!dec_ctx->channel_layout)
dec_ctx->channel_layout =
av_get_default_channel_layout(dec_ctx->channels); // 音频通道布局
_snprintf(args, sizeof(args),
"time_base=%d/%d:sample_rate=%d:sample_fmt=%s:channel_layout=0x%I64x",
dec_ctx->time_base.num, dec_ctx->time_base.den, dec_ctx->sample_rate,
av_get_sample_fmt_name(dec_ctx->sample_fmt),
dec_ctx->channel_layout);
// 创建过滤器实例并将其添加到现有的图形中
ret = avfilter_graph_create_filter(&buffersrc_ctx, buffersrc, "in",
args, NULL, filter_graph);
if (ret < 0) {
av_log(NULL, AV_LOG_ERROR, "Cannot create audio buffer source\n"); // 无法创建音频缓冲区源
goto end;
}
// 创建过滤器实例并将其添加到现有的图形中
ret = avfilter_graph_create_filter(&buffersink_ctx, buffersink, "out",
NULL, NULL, filter_graph);
if (ret < 0) {
av_log(NULL, AV_LOG_ERROR, "Cannot create audio buffer sink\n"); // 无法创建音频缓冲区接收器
goto end;
}
// 用来设置AVOption
ret = av_opt_set_bin(buffersink_ctx, "sample_fmts",
(uint8_t*)&enc_ctx->sample_fmt, sizeof(enc_ctx->sample_fmt),
AV_OPT_SEARCH_CHILDREN);
if (ret < 0) {
av_log(NULL, AV_LOG_ERROR, "Cannot set output sample format\n"); // 无法设置输出样本格式
goto end;
}
ret = av_opt_set_bin(buffersink_ctx, "channel_layouts",
(uint8_t*)&enc_ctx->channel_layout,
sizeof(enc_ctx->channel_layout), AV_OPT_SEARCH_CHILDREN);
if (ret < 0) {
av_log(NULL, AV_LOG_ERROR, "Cannot set output channel layout\n"); // 无法设置输出通道布局
goto end;
}
// 用来设置AVOption
ret = av_opt_set_bin(buffersink_ctx, "sample_rates",
(uint8_t*)&enc_ctx->sample_rate, sizeof(enc_ctx->sample_rate),
AV_OPT_SEARCH_CHILDREN);
if (ret < 0) {
av_log(NULL, AV_LOG_ERROR, "Cannot set output sample rate\n"); // 无法设置输出采样率
goto end;
}
} else {
ret = AVERROR_UNKNOWN;
goto end;
}
/* Endpoints for the filter graph. */
outputs->name = av_strdup("in");
outputs->filter_ctx = buffersrc_ctx;
outputs->pad_idx = 0;
outputs->next = NULL;
inputs->name = av_strdup("out");
inputs->filter_ctx = buffersink_ctx;
inputs->pad_idx = 0;
inputs->next = NULL;
if (!outputs->name || !inputs->name) {
ret = AVERROR(ENOMEM);
goto end;
}
// 将由字符串描述的图形添加到图形中。
if ((ret = avfilter_graph_parse_ptr(filter_graph, filter_spec,
&inputs, &outputs, NULL)) < 0)
goto end;
// 检查有效性并配置图中的所有链接和格式。
if ((ret = avfilter_graph_config(filter_graph, NULL)) < 0)
goto end;
// 给 fctx 进行填充
fctx->buffersrc_ctx = buffersrc_ctx;
fctx->buffersink_ctx = buffersink_ctx;
fctx->filter_graph = filter_graph;
end:
avfilter_inout_free(&inputs);
avfilter_inout_free(&outputs);
return ret;
}
static int init_filters(void)
{
const char *filter_spec;
unsigned int i;
int ret;
filter_ctx = (FilteringContext *)av_malloc_array(ifmt_ctx->nb_streams, sizeof(*filter_ctx));
if (!filter_ctx)
return AVERROR(ENOMEM);
for (i = 0; i < ifmt_ctx->nb_streams; i++) {
filter_ctx[i].buffersrc_ctx = NULL;
filter_ctx[i].buffersink_ctx = NULL;
filter_ctx[i].filter_graph = NULL;
if (!(ifmt_ctx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO
|| ifmt_ctx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO))
continue;
if (ifmt_ctx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO)
filter_spec = "null"; /* 视频直通(虚拟)滤波器 */
else
filter_spec = "anull"; /* 视频直通(虚拟)滤波器 */
ret = init_filter(&filter_ctx[i], ifmt_ctx->streams[i]->codec,
ofmt_ctx->streams[i]->codec, filter_spec);
if (ret)
return ret;
}
return 0;
}
// 编码写入帧
static int encode_write_frame(AVFrame *filt_frame, unsigned int stream_index, int *got_frame) {
int ret;
int got_frame_local;
AVPacket enc_pkt;
int (*enc_func)(AVCodecContext *, AVPacket *, const AVFrame *, int *) =
(ifmt_ctx->streams[stream_index]->codec->codec_type ==
AVMEDIA_TYPE_VIDEO) ? avcodec_encode_video2 : avcodec_encode_audio2;
if (!got_frame)
got_frame = &got_frame_local;
av_log(NULL, AV_LOG_INFO, "Encoding frame\n");
// 编码过滤帧
enc_pkt.data = NULL;
enc_pkt.size = 0;
av_init_packet(&enc_pkt); // 用默认值初始化数据包的可选字段。
ret = enc_func(ofmt_ctx->streams[stream_index]->codec, &enc_pkt,
filt_frame, got_frame);
av_frame_free(&filt_frame); // 释放file_frame
if (ret < 0)
return ret;
if (!(*got_frame))
return 0;
// 准备 enc_pkt
enc_pkt.stream_index = stream_index;
// av_rescale_q_rnd 将64位整数重缩放为2个具有指定舍入的有理数。
enc_pkt.dts = av_rescale_q_rnd(enc_pkt.dts,
ofmt_ctx->streams[stream_index]->codec->time_base,
ofmt_ctx->streams[stream_index]->time_base,
(AVRounding)(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));
enc_pkt.pts = av_rescale_q_rnd(enc_pkt.pts,
ofmt_ctx->streams[stream_index]->codec->time_base,
ofmt_ctx->streams[stream_index]->time_base,
(AVRounding)(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));
enc_pkt.duration = av_rescale_q(enc_pkt.duration,
ofmt_ctx->streams[stream_index]->codec->time_base,
ofmt_ctx->streams[stream_index]->time_base);
av_log(NULL, AV_LOG_DEBUG, "Muxing frame\n");
/* mux encoded frame */
ret = av_interleaved_write_frame(ofmt_ctx, &enc_pkt); // 将数据包写入输出媒体文件,以确保正确的交织。
return ret;
}
// 过滤编码写入帧
static int filter_encode_write_frame(AVFrame *frame, unsigned int stream_index)
{
int ret;
AVFrame *filt_frame;
av_log(NULL, AV_LOG_INFO, "Pushing decoded frame to filters\n"); // 将解码帧推送到过滤器
/* 将解码后的帧推入 filtergraph */
ret = av_buffersrc_add_frame_flags(filter_ctx[stream_index].buffersrc_ctx,
frame, 0);
if (ret < 0) {
av_log(NULL, AV_LOG_ERROR, "Error while feeding the filtergraph\n");
return ret;
}
/* 从 filtergraph 中拉出过滤的帧 */
while (1) {
filt_frame = av_frame_alloc(); // 初始化 filt_frame
if (!filt_frame) {
ret = AVERROR(ENOMEM);
break;
}
av_log(NULL, AV_LOG_INFO, "Pulling filtered frame from filters\n");
// av_buffersink_get_frame 从接收器获取一个带有过滤数据的帧,并将其放入帧中
ret = av_buffersink_get_frame(filter_ctx[stream_index].buffersink_ctx,
filt_frame);
if (ret < 0) {
/* 如果没有更多的输出帧 - returns AVERROR(EAGAIN)
* 如果刷新并且没有更多的帧用于输出 - returns AVERROR_EOF
* 将 ret 置为 0 以将其显示为正常过程完成
*/
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
ret = 0;
av_frame_free(&filt_frame); // 释放 filt_frame
break;
}
filt_frame->pict_type = AV_PICTURE_TYPE_NONE;
ret = encode_write_frame(filt_frame, stream_index, NULL);
if (ret < 0)
break;
}
return ret;
}
static int flush_encoder(unsigned int stream_index)
{
int ret;
int got_frame;
if (!(ofmt_ctx->streams[stream_index]->codec->codec->capabilities &
CODEC_CAP_DELAY))
return 0;
while (1) {
av_log(NULL, AV_LOG_INFO, "Flushing stream #%u encoder\n", stream_index);
ret = encode_write_frame(NULL, stream_index, &got_frame);
if (ret < 0)
break;
if (!got_frame)
return 0;
}
return ret;
}
int _tmain(int argc, _TCHAR* argv[])
{
int ret;
AVPacket packet;
AVFrame *frame = NULL;
enum AVMediaType type;
unsigned int stream_index;
unsigned int i;
int got_frame;
int (*dec_func)(AVCodecContext *, AVFrame *, int *, const AVPacket *);
if (argc != 3) {
av_log(NULL, AV_LOG_ERROR, "Usage: %s <input file> <output file>\n", argv[0]);
return 1;
}
av_register_all(); // 注册ffmpeg所有编解码器
avfilter_register_all(); // 初始化过滤系统。注册所有内置过滤器。
if ((ret = open_input_file(argv[1])) < 0) // 打开输入文件,并初始化相关的结构体。
goto end;
if ((ret = open_output_file(argv[2])) < 0) // 打开输出文件,并初始化相关的结构体。
goto end;
if ((ret = init_filters()) < 0) // 初始化AVFilter相关的结构体。
goto end;
// 读取所有数据包
while (1) {
if ((ret = av_read_frame(ifmt_ctx, &packet)) < 0) // 从输入文件中读取一个AVPacket。
break;
stream_index = packet.stream_index;
type = ifmt_ctx->streams[packet.stream_index]->codec->codec_type;
av_log(NULL, AV_LOG_DEBUG, "Demuxer gave frame of stream_index %u\n",
stream_index);
if (filter_ctx[stream_index].filter_graph) {
av_log(NULL, AV_LOG_DEBUG, "Going to reencode&filter the frame\n");
frame = av_frame_alloc(); // AVFrame结构,av_frame_alloc申请内存,av_frame_free释放内存
if (!frame) {
ret = AVERROR(ENOMEM);
break;
}
// 将64位整数重缩放为2个具有指定舍入的有理数。
// 返回重新缩放的值a,或者如果设置了AV\u ROUND\u PASS\u MINMAX并且a是INT64_MIN或INT64_MAX则a以不变的方式通过。
packet.dts = av_rescale_q_rnd(packet.dts,
ifmt_ctx->streams[stream_index]->time_base,
ifmt_ctx->streams[stream_index]->codec->time_base,
(AVRounding)(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));
packet.pts = av_rescale_q_rnd(packet.pts,
ifmt_ctx->streams[stream_index]->time_base,
ifmt_ctx->streams[stream_index]->codec->time_base,
(AVRounding)(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));
// 视频解码或者音频解码
dec_func = (type == AVMEDIA_TYPE_VIDEO) ? avcodec_decode_video2 :
avcodec_decode_audio4;
ret = dec_func(ifmt_ctx->streams[stream_index]->codec, frame,
&got_frame, &packet);
if (ret < 0) {
av_frame_free(&frame);
av_log(NULL, AV_LOG_ERROR, "Decoding failed\n");
break;
}
if (got_frame) {
frame->pts = av_frame_get_best_effort_timestamp(frame);
ret = filter_encode_write_frame(frame, stream_index); // 编码一个AVFrame。
av_frame_free(&frame);
if (ret < 0)
goto end;
} else {
av_frame_free(&frame);
}
} else {
// 重新复制此帧而不重新编码
packet.dts = av_rescale_q_rnd(packet.dts,
ifmt_ctx->streams[stream_index]->time_base,
ofmt_ctx->streams[stream_index]->time_base,
(AVRounding)(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));
packet.pts = av_rescale_q_rnd(packet.pts,
ifmt_ctx->streams[stream_index]->time_base,
ofmt_ctx->streams[stream_index]->time_base,
(AVRounding)(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));
ret = av_interleaved_write_frame(ofmt_ctx, &packet); // 将数据包写入输出媒体文件,以确保正确的交织。
if (ret < 0)
goto end;
}
av_free_packet(&packet);
}
/* flush filters and encoders */
for (i = 0; i < ifmt_ctx->nb_streams; i++) {
/* flush filter */
if (!filter_ctx[i].filter_graph)
continue;
ret = filter_encode_write_frame(NULL, i); // 编码一个AVFrame。
if (ret < 0) {
av_log(NULL, AV_LOG_ERROR, "Flushing filter failed\n");
goto end;
}
/* flush encoder */
ret = flush_encoder(i);
if (ret < 0) {
av_log(NULL, AV_LOG_ERROR, "Flushing encoder failed\n");
goto end;
}
}
av_write_trailer(ofmt_ctx); // 写文件尾
end:
av_free_packet(&packet);
av_frame_free(&frame);
for (i = 0; i < ifmt_ctx->nb_streams; i++) {
avcodec_close(ifmt_ctx->streams[i]->codec);
if (ofmt_ctx && ofmt_ctx->nb_streams > i && ofmt_ctx->streams[i] && ofmt_ctx->streams[i]->codec)
avcodec_close(ofmt_ctx->streams[i]->codec);
if (filter_ctx && filter_ctx[i].filter_graph)
avfilter_graph_free(&filter_ctx[i].filter_graph);
}
av_free(filter_ctx);
avformat_close_input(&ifmt_ctx);
if (ofmt_ctx && !(ofmt_ctx->oformat->flags & AVFMT_NOFILE))
avio_close(ofmt_ctx->pb);
avformat_free_context(ofmt_ctx);
if (ret < 0)
av_log(NULL, AV_LOG_ERROR, "Error occurred\n");
return (ret? 1:0);
2.8 YUV讲解
2.8.1 什么是YUV
YUV,是一种颜色编码方法,“Y”表示明亮度,也就是灰阶值,“U”和“V”表示的则是色度,作用是描述影像色彩及饱和度,用于指定像素的颜色。
2.8.2 常见的YUV格式
YUV420格式:4:2:0表示2:1的水平取样,垂直2:1采样。
此外还有:
4:4:4表示完全取样。
4:2:2表示2:1的水平取样,垂直完全采样。
4:1:1表示4:1的水平取样,垂直完全采样。