为使解码过程不会受到解封装过程进展的影响,解封装和解码一般并行操作,两者之间通过缓存数据进行交互。如上图所示:
解封装线程和解码线程可以套用生产者和消费者模式来实现线程间的通信。
为方便在多个模块中实现线程间的通信,可以把生产者和消费者模式简化为一个类:该类具有等待唤醒机制。
具有等待唤醒机制的类
为方便对 std::condition_variable 等资源进行管理、及简化代码实现过程,实现一个具有等待唤醒机制的类。
wait_for
std::condition_variable 的 wait_for 方法会导致当前线程阻塞直至条件变量被通知、或者虚假唤醒发送、或者超时返回。
wait_for 返回值说明:
- 不带 lambda 表达式的版本:若设定的超时时间到了还没被唤醒则返回 std::cv_status::timeout,否则返回 std::cv_status::no_timeout。
- 带 lambda 表达式的版本:若设定的超时时间到了 lambda 表达式仍求值为 false 则返回 false,否则返回 true。
- 带 lambda 表达式的版本执行时会先对 lambda 表达式求值,若为 true 则不阻塞;被唤醒时也会对 lambda 表达式求值,若为 false 则继续阻塞。
代码示例
class WaitAndNotify
{
public:
// 超时时间到、或者 notify 会被唤醒
// wait时 pred 为 true 才会被唤醒
bool wait_for(const std::chrono::milliseconds& timeout,std::function<bool()> pred = [] { return false; })
{
std::unique_lock<std::mutex> lock(mutex);
return cv.wait_for(lock, timeout, std::move(pred)) ;
}
void notify_all()
{
std::unique_lock<std::mutex> lock(mutex);
cv.notify_all();
}
private:
std::mutex mutex;
std::condition_variable cv;
};
完善 FFmpegPlayer 类
解封装线程没必要一直运行,在缓存够一定的数据后可以休眠一段时间以减少 CPU 资源的占用。
添加 WaitAndNotify 成员
添加 WaitAndNotify 类型的成员,以增加休眠、唤醒机制,代码如下:
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 队列
WaitAndNotify packetFullWait;// packet 队列满等待
};
完善 readPacket() 方法
若视频或者音频缓存队列满了,则等待 10ms,直到被唤醒或者超时,代码如下:
int FFmpegPlayer::readPacket() {
while(true)
{
if( abort_request ) break;//用户退出
// 强制退出 不阻塞
// 两个队列都不满 不阻塞
auto pred = [&]() { return abort_request ||( !video_packet_Queue->IsFull() && !audio_packet_Queue->IsFull() ); };//返回 false 才会阻塞
if( !packetFullWait.wait_for(std::chrono::milliseconds(10), pred))//超时了,或者唤醒了 继续
{
std::cout << "packetQueue is full wait ,timeout"<< std::endl;
}
else
{
std::cout << "packetQueue is not full,no need wait"<< std::endl;
}
if( abort_request == 1 ) break;//强制退出唤醒
if (video_packet_Queue->IsFull() || audio_packet_Queue->IsFull())
{
continue;//唤醒后,缓存还是满
}
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;
}
运行结果如下:
作者:litanyuan | 来源:公众号——编程猿来如此
版权声明:本文内容转自互联网,本文观点仅代表作者本人。本站仅提供信息存储空间服务,所有权归原作者所有。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至1393616908@qq.com 举报,一经查实,本站将立刻删除。