1、前言
如上图所示,在上一篇文章中介绍了视频播放的基本原理。本文继续使用 FFmpeg 来实现其中音视频解封装功能。
2、背景
2.1 封装格式
封装格式(也叫容器)就是将已经编码压缩好的视频流、音频流及字幕按照一定的方案放到一个文件中,便于播放软件播放。一般来说,视频文件的后缀名就是它的封装格式,如 MP4、flv 等,如下图所示:
2.2 解封装
解封装就是和视频封装相反的过程,即把一个流媒体文件,拆解成音频数据和视频数据,一般被拆解成 H.264 编码的视频码流和 AAC 编码的音频码流,如下图所示:
2.3 解封装实现流程
使用 FFmpeg 实现解封装就是在在打开视频文件后,使用 av_read_frame 接口不断读取数据包的过程,如下图所示:
3、解封装代码实现
【学习地址】:FFmpeg/WebRTC/RTMP/NDK/Android音视频流媒体高级开发
【文章福利】:免费领取更多音视频学习资料包、大厂面试题、技术视频和学习路线图,资料包括(C/C++,Linux,FFmpeg webRTC rtmp hls rtsp ffplay srs 等等)有需要的可以点击1079654574加群领取哦~
3.1 概述
本文在之前 FFmpegPlayer 类的基础上,进一步完善相关成员变量及方法,以实现解封装功能。
3.2 完善 FFmpegPlayer 类
增加一个成员变量 packet ,用于接收和暂存读取到的数据,完善后的 FFmpegPlayer 类如下:
class FFmpegPlayer {
public:
explicit FFmpegPlayer(const char* m_url);
~FFmpegPlayer();
public:
bool openFile();//打开文件
void startDecode();//开启循环解码
void abortDecode();//中断解码
private:
int readOnePacket();//读一个包
int readPacket();//循环读包
private:
std::string url;//文件路径
AVFormatContext* formatContext = nullptr;//封装格式上下文
AVPacket packet{}; //用于读包
std::thread * read_thread = nullptr;//数据读取线程
bool abort_request{false};//强制结束
bool readEof{false};//读包结束
int video_index{-1};//视频流索引
int audio_index{-1};//音频流索引
};
3.3 readOnePacket 方法实现
readOnePacket 方法用于读取一个数据包,并返回是否读取成功的标记,实现如下:
int FFmpegPlayer::readOnePacket()
{
if( abort_request ) return -1;//响应用户中断操作
int ret = av_read_frame( formatContext, &packet); //读取一个数据包
if( ret < 0 )
{
if( ret == AVERROR_EOF )//读包结束
{
readEof = true;
}
else//出错了
{
return -1;
}
}
else
{
readEof = false;
}
return ret;
}
3.4 readPacket 方法实现
readPacket 方法实现了循环读包,调用 readOnePacket 读取数据包并进行处理,实现如下:
int FFmpegPlayer::readPacket()
{
while(true)
{
if( abort_request ) break;//用户退出
std::shared_ptr<AVPacket> pktClear(&packet, [](AVPacket *p "")
{
av_packet_unref(p);
});//用来确保每次 av_read_frame 后 pkt 缓存被清理了
int ret = readOnePacket();
if( ret == 0 )
{
if (packet.stream_index == video_index)
{
std::cout << "读取到了一个视频包,pts :" << packet.pts << std::endl;
}
else if (packet.stream_index == audio_index)
{
std::cout << "读取到了一个音频包,pts :" << packet.pts << std::endl;
}
}
}
return 0;
}
3.5 startDecode 方法实现
创建一个线程,用于循环读包,实现如下:
void FFmpegPlayer::startDecode()
{
read_thread = new std::thread( &FFmpegPlayer::readPacket, this );
}
3.6 abortDecode 方法实现
用户主动中断视频播放接口,更改中断标记并结束线程,实现如下:
void FFmpegPlayer::abortDecode()
{
abort_request = 1;
if( read_thread && read_thread->joinable())
read_thread->join();
}
4、代码运行示例
文件打开成功后,调用解复用接口,代码如下:
#include "FFmpegPlayer.h"
int main() {
const char * url = "C:\\Lzc\\WorkCode\\test.mkv";
FFmpegPlayer * player = new FFmpegPlayer(url);
if( player->openFile())
{
std::cout << "文件打开成功!"<<std::endl;
player->startDecode();
}
system("pause");
return 0;
}
运行结果如下: