音视频开发学习
- 一、音视频开发基础知识
- 二、ffmpeg编程示例
- 1. 用C++ 编程将H264 视频转为YUV格式
- 1)、具体代码步骤:
- 2)、具体成员及方法
- 1. AVFormatContext:
- 2. avformat_alloc_context(void);
- 3. avformat_open_input():
- 4.avformat_find_stream_info():
- 5. AVCodec:
- 6. av_find_best_stream():
- 7. avcodec_alloc_context3():
- 8. AVCodecContext:
- 9. AVCodecParameters:
- 10. avcodec_parameters_to_context():
- 11.avcodec_open2():
- 12.AVPacket:
- 13. av_packet_alloc():
- 14. AVFrame:
- 15. av_frame_alloc():
- 16. av_frame_get_buffer:
- 17. SwsContext:
- 18. sws_getContext():
- 19.av_read_frame():
- 20.avcodec_send_packet():
- 21.avcodec_receive_frame():
- 22.sws_scale():
- 2. 用C++ 调用 ffmpeg接口实现音频+视频复用
一、音视频开发基础知识
1. 视频基础理论
-
视频的表示:
① RGB格式:数字图像用RGB表示,每个RGB分量用8bit表示 = 256,采用的数值越大,表现出来的现象越精细。
② YUV格式:数字图像也可以用YUV,Y表示明亮度(灰阶值)、U、V表示的色度,作用是描述影像色彩及饱和度。主要用于视频信号的压缩、传输和存储。YUV格式下又有两大类:planar、packed
·packed:packed格式下每个像素点下的Y、U、V三个分量的数值是相邻连续的
·planar:planar格式下每个像素点下的Y、U、V三个分量的数值是分开存储的
·YUV444:单个像素点下Y、U、V分量之间一一对应
·YUV422:两个相邻Y对应一组UV分量
·YUV420:4个相邻的Y对应一组UV分量
采用YUV420P是可以将原先采用RGB编码格式的图像/视频内存减少一半 -
视频的压缩/编码:压缩分为帧内压缩与帧间压缩,压缩的目的是使电影、图像的容量减小
①帧内压缩:相邻的像素点做同一个表示
②帧间压缩:记录两个变化不大的帧偏移位置 -
YUV->RGB转换公式:因为YUV最终还是要转到RGB显示,容易出现解码异常的问题:YUV各分量的值都为0
从低分辨率->高分辨率画质不会得到提升 -
I B P 帧的概念
①I 帧:不需要参考其他画面即可生成自己的图像
②P帧:
③B帧:
-
常见视频压缩算法:H264、H265
2. 音频基础理论
-
音频采样率/采样精度:一秒钟采样44.1khz或者48khz
如左图音频是连续的一个波值,右图则是离散采样后的效果
通道:音源越多越真实。- 声卡播放原理:声卡必须固定参数,声音/视频参数传到系统里面中就会先做MIX操作(转成统一声卡需要的格式,然后混音)
- 声卡播放原理:声卡必须固定参数,声音/视频参数传到系统里面中就会先做MIX操作(转成统一声卡需要的格式,然后混音)
-
交错模式/非交错模式
①交错模式:左右左右
②非交错模式:左、右
封装、解码、重采样、像素格式
封装:从文件中把音频/视频读取出来
解码:将读取出来的音频/视频进行解压,视频需要转换成显卡适配的格式。音频则需要转换成声卡所需的格式。才能播放
- 封装格式:
MPEG-4 封装标准:
MPEG-4是一套用于音频、视频信息的压缩编码标准
MPEG-4 Part 14 MPEG-4文件格式Part 15AVC文件格式,常用的mp4文件就属于MPEG-4 Part 14
采用 H264 做视频压缩,采用AVC Part10做音频压缩
AVI 封装标准:压缩标准可任意选择
FLV ts 流媒体格式 - 编码格式:
视频 H264 ( AVC Part10 ) , wmv ,XviD ( Part2 ) , mjpeg
音频 aac MP3 ape flac - 封装格式/编码格式 解码过程:
封装格式头:头部装入视频/音频 流的信息
时评编码帧:
二、ffmpeg编程示例
1. 用C++ 编程将H264 视频转为YUV格式
1)、具体代码步骤:
(一)、 初始化全局AVFormatContext对象
-
用 avformat_alloc_context() 创建AVFormatContext,它是存储音视频封装格式中包含的信息的结构体,也是FFmpeg中统领全局的结构体,对文件的封装、编码操作从这里开始。
//创建上下文对象 AVFormatContext* formatContext = avformat_alloc_context();
(二)、 打开流媒体获取流媒体具体信息并设置到AVFormatContext对象中
-
用avformat_open_input() 打开流媒体文件:
//打开流媒体文件 char* inputFile = "C:\\Users\\Administrator\\Desktop\\Titanic.h264"; avformat_open_input(&formatContext,inputFile,NULL,NULL);
-
用avformat_find_stream_info() 获取媒体文件中的具体信息
//获取媒体文件中的具体流信息 avformat_find_stream_info(formatContext,NULL);
(三)、 查找/配置/开启 处理视频流的解码器
-
用av_find_best_stream查找适配视频流的流索引并配置该流索引下的解码器,返回该流的索引值
const AVCodec * codec; // 解码器 //查找属于AVMEDIA_TYPE_VIDEO流索引,并配置该流索性下的解码器 int videoStreamIndex = av_find_best_stream(formatContext,AVMEDIA_TYPE_VIDEO,-1,-1,&codec,-1);
-
用avcodec_alloc_context3根据查到的解码器创建分配空间以及初始化解码器上的下文信息
AVCodecContext * codecContext = avcodec_alloc_context3(codec);
-
用videoStreamIndex 查到 formatContext中的属于AVMEDIA_TYPE_VIDEO类型解码器的信息
//获取该视频类型流下解码器的具体参数 AVCodecParameters* codecParameters = formatContext->streams[videoStreamIndex]->codecpar;
-
用avcodec_parameters_to_context()将参数信息设置到解码器上下文信息对象中
//将这些参数设置到解码器上下文中 avcodec_parameters_to_context(codecContext,codecParameters);
-
用avcodec_open2()打开解码器:
//打开解码器 avcodec_open2(codecContext,codec,NULL);
(四)、解码前/后帧数据的缓存位置创建
-
用av_packet_alloc() 创建packet,用于存储解码前的数据
AVPacket *packet = av_packet_alloc();
-
用av_frame_alloc()创建Frame 并设置参数用于存储解码后的数据
//创建Frame并设置参数,用于存储解码后的数据 AVFrame * frame = av_frame_alloc(); frame->width = codecParameters->width; frame->height = codecParameters->height; frame->format = codecParameters->format; av_frame_get_buffer(frame,32); AVFrame *yuvFrame = av_frame_alloc(); yuvFrame->width = codecParameters->width; yuvFrame->height = codecParameters->height; yuvFrame->format = AV_PIX_FMT_YUV420P; av_frame_get_buffer(yuvFrame,32);
(五)、设置重采样上下文
-
利用sws_getContext()设置重采样上下文
SwsContext* imgSwsContext = sws_getContext(codecParameters->width, codecParameters->height, static_cast<AVPixelFormat>(codecParameters->format), codecParameters->width, codecParameters->height, AV_PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL);
(六)、读取媒体流中每一帧数据并进行编解码
-
用av_read_frame()+while 读取每一帧数据
while(av_read_frame(formatContext,packet)>=0){ //如果该帧不是视频流则丢弃 if (packet->stream_index!=videoStreamIndex){ av_packet_unref(packet); //当不用这个packet了,你需要释放av_packet_unref(&packet), 此时包头就会恢复成默认值. } }
-
用avcodec_send_packet() 发送编码数据包。该函数只是将一个packet放入到队列中等待解码。并不是一个packet,就代表一个frame,解码操作是在该函数中进行的。完成此操作后,解码后的数据放在avctx->internal->buff_frame中。
avcodec_send_packet(codecContext,packet);
-
用avcodec_receive_frame() +while 接收解码后数据,并写入到目标文件中
while (avcodec_receive_frame(cod_ctx, frame) >= 0) { printf("decode frame count = %d\n" , count++); sws_scale(imgSwsContext,frame->data,frame->linesize,0,codecParameters->height,yuvFrame->data,yuvFrame->linesize); int y_size = yuv_frame->width * yuv_frame->height; fwrite(yuv_frame->data[0], 1, y_size, out_fb); fwrite(yuv_frame->data[1], 1, y_size/4, out_fb); fwrite(yuv_frame->data[2], 1, y_size/4, out_fb); } av_packet_unref(packet);
(七)、释放对象:
fclose(outputFile);
av_frame_free(&destFrame);
av_frame_free(&srcFrame);
avcodec_close(codecContext);
avformat_close_input(&formatContext);
2)、具体成员及方法
1. AVFormatContext:
AVFormatContext是存储音视频封装格式中包含的信息的结构体,也是FFmpeg中统领全局的结构体,对文件的封装、编码操作从这里开始。
2. avformat_alloc_context(void);
该方法主要完成
①AVFormatContext的空间分配,注意分配在堆上;
②给AVFormatContext的成员赋默认值;
③完成AVFormatContext内部使用对象AVFormatInternal结构体的空间分配及其部分成员字段的赋值
3. avformat_open_input():
打开输入流,读取媒体文件的头信息,但不打开编码器,需要使用编码相关的函数打开编码器;
4.avformat_find_stream_info():
从读取到的媒体文件的音视频中获取流的具体信息。
5. AVCodec:
是存储编解码器信息的结构体
6. av_find_best_stream():
用于找到用户想要寻找的流的信息,比如查找视频流,音频流,字幕流,如果没有指定解码器则使用默认的解码器,如果对应的流没有相应的解码器支持则会被忽略。
7. avcodec_alloc_context3():
为AVCodecContext分配内存空间,并设置默认的值
8. AVCodecContext:
AVCodecContext结构表示程序运行的当前Codec使用的上下文,记录了所有Codec共有的属性(并且是在程序运行时才能确定其值)和关联其他结构的字段。不管是编码,还是解码都会用到
9. AVCodecParameters:
解码结构体的结构体参数,解码之前都需要通过它来初始化AVCodecContext
10. avcodec_parameters_to_context():
将音频流信息拷贝到新的AVCodecContext结构体中
11.avcodec_open2():
打开解码器
12.AVPacket:
是存储压缩编码数据相关信息的结构体。保存了压缩后的数据以及这些数据的信息,比如显示时间戳pts,解码时间戳dts,每一帧持续的时间duration以及媒体流的索引等
13. av_packet_alloc():
简单的创建一个AVPacket,将其字段设为默认值
14. AVFrame:
AVFrame结构体一般用于存储原始数据(即非压缩数据,例如对视频来说是YUV,RGB,对音频来说是PCM),此外还包含了一些相关的信息。
15. av_frame_alloc():
分配并默认初始化一个AVFrame对象
16. av_frame_get_buffer:
对于视频frame,只用在调用函数之前设置好图像的宽高和图像格式等信息,音频设置好nb_samples,channel_layout 和采样格式等信息,此函数将填充AVFrame.data和AVFrame.buf数组,并在必要时分配和填充AVFrame.extended_data和AVFrame.extended_buf。而且采用这种方式填充AVFrame的data后,在最后释放时调用av_frame_free可以直接释放掉AVFrame的所有内存,不会出现内存泄漏的问题。
17. SwsContext:
视频格式转换上下文,编解码中可能需要对视频格式重新设置,就需要用到SwsContext。
18. sws_getContext():
设置视频转换SwsContext 对象的上下文。
19.av_read_frame():
用是读取码流中的音频若干帧或者视频一帧。例如,解码视频的时候,每解码一个视频帧,需要先调用 av_read_frame()获得一帧视频的压缩数据,然后才能对该数据进行解码
20.avcodec_send_packet():
负责把AVpacket数据包发送给解码器,avcodec_receive_frame()则从解码器取出一帧AVFrame数据。返回0,代表解码成功;返回EAGAIN,代表当前状态无可输出的数据;返回EOF,代表到达文件流结尾;返回INVAL,代表解码器未打开或者当前打开的是编码器;返回INPUT_CHANGED,代表输入参数发生变化,比如width和height改变。
21.avcodec_receive_frame():
函数的主要功能是从解码队列中取出一帧
22.sws_scale():
主要用于处理图片像素数据的类库。可以完成图片像素格式的转换,图片的拉伸等工作