编程开发

音视频知识梳理

2021-02-28  本文已影响0人  Johnny_Wu

一、整体播放策略

(1)iOS自带的播放控件
- AVPlayer、AVQueuePlayer
只能通过路径播放,可以播放本地视频与云视频
- AVSampleBufferDisplayLayer
可以实现按帧播放
(2)第三方
- ijkPlayer
默认只支持按路径播放,可以通过IO管道的方式,改为按帧播放方式
(3)组合方式
系统硬解VideoToolBox/ffmpeg软解 + OpenGL显示

二、VideoToolBox硬解

CMSampleBuffer = CMTime + FormatDesc(sps,pps) + CMBlockBuffer
CMBlockBuffer:编码的图像数据结构
CVPixelBuffer:解码后的图像数据结构

VTDecompressionSessionRef decodeSession

(CMSampleBuffer+decodeSession)—>VTDecompressionSessionDecodeFrame—>CVPixelBuffer

三、ffmpeg解码

AVPacket—>AVFrame
1、创建AVCodec codec = avcodec_find_decoder(AV_CODEC_ID_H264)
2、创建AVCodecContext codecCtx = avcodec_alloc_context3(codec);
3、avcodec_open2(codecCtx, codec, NULL)
4、视频帧组装成 AVPacket
5、avcodec_send_packet(codecCtx, &packet)
6、avcodec_receive_frame(codecCtx, AVFrame)

(1)AVFrame中取到YUV数据(这里的是非交叉的)
Y:
AVFrame->data[0] 存Y数据的buffer
AVFrame->linesize[0] Y数据一段的长度,一般等于像素width。有几段呢,就得看height

AVFrame->data[1] 存U数据的buffer
AVFrame->linesize[1] U数据一段的长度,一般等于像素width/2。有几段呢,就得看height/2

AVFrame->data[2] 存V数据的buffer
AVFrame->linesize[2] V数据一段的长度,一般等于像素width/2。有几段呢,就得看height/2

比如存到NDData里面:
width = MIN(linesize, width);
NSMutableData *md = [NSMutableData dataWithLength: width * height];
Byte *dst = (Byte *)md.mutableBytes;
for (NSUInteger i = 0; i < height; ++i) {
memcpy(dst, src, width);
dst += width;
src += linesize;
}
return md;

AVFrame->data只能以linesize作为取值累积量,这样才不会出现越界或者取错数据

(2)IOS硬解得到的CVImageBufferRef,其中的YUV是交叉存储的
CVImageBufferRef imageBuffer =CMSampleBufferGetImageBuffer(sampleBuffer);

四、ffmpeg其他一些重要信息

(1)主要结构体
AVFormatContext:文件信息上下文,其中就包含了流信息结构AVStream

AVStream:流信息,其中就包含具体的音视频格式信息上下文AVCodecContext

AVCodecContext:具体的音视频格式信息。要解码音视频,需要从这里拿到相关信息,比如AVCodecID

AVPacket:未解码的音视频内容。比如视频的每一帧数据,就体现在一个packet中

AVFrame:解码后的音视频内容

(2)H264的两种格式 Annex-B与AVCC
- Annex-B
0000000167(SPS)+0000000168(PPS)+0000000165(IDR)+其他帧

- AVCC
extradata+NAL长度+NAL+NAL长度+NAL。。。
NAL长度所占字节、SPS与PPS等包含在extradata中

(3)extradata
解码AVCC需要与视频合成都需要extradata,如何构造extradata
- extradata(Annex-B)
- extradata(AVCC)
- extradata(Annex-B) 转换为 extradata(AVCC)

(4)timebase
ffmpeg中的一种时间单位,包含了与秒的对应关系,都可以转换为秒。
- pts,dts,duration
pts * timebase = 以秒为单位的值

(5)解码流程
- 解Annex-B的H264
- 解AVCC的H264

(6)编码流程
- 生成AVCodecContext,要设置分辨率,pix_fmt等参数
- avcodec_open2(codecCtx, codec, NULL)
- avcodec_send_frame(codecCtx, frame);
- avcodec_receive_packet(codecCtx, pkt);

(7)H264 合成mp4流程
- avformat_alloc_output_context2 创建输出的文件
- avformat_new_stream 创建视频流
- 构造AVCodecContext,赋值给AVStream->codecpar
- 设置timebase,AVStream->time_base
- avio_open打开输出文件
- avformat_write_header写入头
- av_interleaved_write_frame 写入packet
- av_write_trailer 写入结尾

五、OpenGL

简单说就是直接操作GPU进行图片的渲染。OpenGL有自己的渲染管线,提供给外部修改的有顶点处理和片元处理。
顶点处理:我们可以为OpenGL提供顶点,相当于构建了图片的外部框架(顶点坐标)
片元处理:提供图片纹理填充刚才构建的外部框架的内容 (通过纹理坐标可以控制图片的方向)
然后OpenGL就帮我们写入到帧缓冲区:glDraw

帧缓冲区不能直接显示,还得关联一个渲染缓冲区,渲染缓冲区又与某个CAEAGLLayer对应。这样就可以显示出来:presentRenderbuffer

传值:CPU的值,先传到GPU,GPU再传给OpenGL绘制

OpenGL是通过shader语言操作的,通过shader语音来操控顶点与片元

六、ijkPlayer的整体流程

三个线程 读线程+解码线程+显示线程
读线程:不断从视频文件通过ffmpeg读取到AVPacket,存到videoq队列里面
解码线程:从videoq拿出AVPacket,通过ffmpeg解码得到AVFrame,然后转化为Frame结构体,存到picq队列
显示线程:不断从picq取出Frame,然后通过OpenGL渲染

七、音频相关

每个采样都得用一个数据类型来存储,可以是float、uint16等。这个会决定音频的大小
1、音频解码

nb_samples:一包(帧)acc的一个channel有多少采样。解码成pcm之后,就是通过这些采样来计算buf大小,用来存解码后的pcm数据(av_samples_get_buffer_size)注:一般1024个pcm数据作为ACC的一帧,一个(一帧frame)pcm就是一个采样(非交错存储)

一般解码后的pcm可能需要重采样,比如sample_fmt是16或32等,或者采样,声道不一致,需要通过swr_convert转换

非交错的存储方式:NonInterleaved,一个frame只存一个channel,所以frame的大小等于一个channel的采样大小
交错的存储方式:Interleaved,一个frame存多个channel的数据,所以frame = sampleSize * channel

2、音频编码

3、音频播放

上一篇下一篇

猜你喜欢

热点阅读