在之前的文章中实现了打开视频文件并进行解封装的功能,解封装之后即可进行解码操作。
为使解码过程不会受到解封装过程进展的影响,解封装和解码一般并行操作,两者之间通过缓存数据进行交互。
背景
线程安全队列
由于 STL 中的容器是非线程安全的,因此要实现解封装和解码的并行操作需要实现一个线程安全的队列。
由于 std::deque 可以方便的在头尾进行数据的读写,此处选用 std::deque 来简单实现一个线程安全的队列。
AVPacket 和 AVFrame
在 FFmpeg 中解码前和解码后的数据分别使用 AVPacket 和 AVFrame 进行存储。
AVPacket 及 AVFrame 是采用引用计数的方式进行内部资源的管理,在对缓存数据进行读写时要特别注意数据的释放问题,以避免内存泄漏。
成员函数模板特例化
由于 AVPacket 和 AVFrame 资源管理的接口不同,为避免特例化类的所有成员,仅把相关方法通过类的成员函数模板特化来实现。
缓存队列的实现
此处实现的缓存队列仅满足对 AVPacket 和 AVFrame 的管理,且读写两个并发操作互相不受影响即可。
接口设计
template <typename T>
class Queue
{
private:
std::deque<T*> queue;
mutable std::mutex mutex;//访问互斥信号量
int maxSize{0};//最大容量
public:
Queue() = default;
explicit Queue(int m_size ): maxSize(m_size){}
~Queue() = default;
bool IsEmpty() const;//判断队列是否为空
int size() const;//当前元素个数
bool IsFull() const;//判断队列是否已满
void pushBack( T* t );//尾部添加元素
void pushFront( T* t );//头部添加元素
T * front();//读取首元素
T * back();//读取尾元素
T * operator[](int i);//按下标读取元素
void popBack();//删除尾元素
void popFront();//删除首元素
void clear();//清空队列
};
通用方法实现
为实现基本的线程安全保存,所有的成员方法必须加锁,实现如下:
bool IsEmpty() const
{
std::unique_lock<std::mutex> lock(mutex);
return queue.empty();
}//判断队列是否为空
int size() const
{
std::unique_lock<std::mutex> lock(mutex);
return queue.size();
}//当前元素个数
bool IsFull() const
{
std::unique_lock<std::mutex> lock(mutex);
return maxSize <= queue.size();
}//判断队列是否已满
void pushBack( T* t )
{
std::unique_lock<std::mutex> lock(mutex);
queue.push_back(t);
}//尾部添加元素
void pushFront( T* t )
{
std::unique_lock<std::mutex> lock(mutex);
queue.push_front(t);
}//头部添加元素
成员函数模板特例化
涉及到数据读取、删除等资源管理相关操作,要进行成员函数模板特例化实现,以 front() 方法的实现为例:
- 成员函数实现为模板
T * front()
{
return frontImpl<T>();
}//读取首元素
private:
template <typename C>
void popFrontImpl();
- 进行 AVPacket 特例化
template <>
void popFrontImpl<AVPacket>()
{
std::unique_lock<std::mutex> lock(mutex);
if (!queue.empty())
{
auto packet = queue.front();
av_packet_unref(packet);
av_packet_free(&packet);
queue.pop_front();
}
}
- 进行 AVFrame 特例化
template <>
void popFrontImpl<AVFrame>()
{
std::unique_lock<std::mutex> lock(mutex);
if (!queue.empty())
{
auto frame = queue.front();
av_frame_unref(frame);
av_frame_free(&frame);
queue.pop_front();
}
}
缓存队列使用示例
完善 FFmpegPlayer 类
在 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};//音频流索引
Queue<AVPacket>* video_packet_Queue{ nullptr };//视频 packet 队列
Queue<AVPacket>* audio_packet_Queue{ nullptr };//视频 packet 队列
};
初始化队列大小
在 FFmpegPlayer 类构造函数中初始化两个缓存队列,代码如下:
FFmpegPlayer::FFmpegPlayer(const char *m_url):
url(m_url) ,
video_packet_Queue(new Queue<AVPacket>(20)),
audio_packet_Queue(new Queue<AVPacket>(20))
{
}
完善 readPacket() 方法
使用 av_packet_move_ref 方法转移对资源的引用、以保证资源的正确释放,代码如下:
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 && !video_packet_Queue->IsFull() )
{
AVPacket* m_packet = av_packet_alloc();
av_packet_move_ref(m_packet,&packet);
video_packet_Queue->pushBack(m_packet);
std::cout << "video_packet_Queue size " << video_packet_Queue->size() << std::endl;
}
else if (packet.stream_index == audio_index && !audio_packet_Queue->IsFull())
{
AVPacket* m_packet = av_packet_alloc();
av_packet_move_ref(m_packet,&packet);
audio_packet_Queue->pushBack(m_packet);
std::cout << "audio_packet_Queue size " << audio_packet_Queue->size() << std::endl;
}
}
}
return 0;
}
代码运行示例
文件打开成功后,调用解复用接口,代码如下:
#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;
}
运行结果如下:
作者:litanyuan | 来源:公众号——编程猿来如此
版权声明:本文内容转自互联网,本文观点仅代表作者本人。本站仅提供信息存储空间服务,所有权归原作者所有。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至1393616908@qq.com 举报,一经查实,本站将立刻删除。