框架
一、解复用模块
1.解封装
2.加入log程序
为日志程序,直接拖入项目。
3.创建线程
demuxthread.h代码
#ifndef DEMUXTHREAD_H
#define DEMUXTHREAD_H
#include "thread.h"
#ifdef __cplusplus ///
extern "C"
{
// 包含ffmpeg头文件
#include "libavutil/avutil.h"
#include "libavformat/avformat.h"
}
#endif
class DemuxThread : public Thread
{
public:
DemuxThread();
~DemuxThread();
int Init(const char *url);
int Start();
int Stop();
void Run();
private:
char err2str[256] = {0};
std::string url_;// 文件名
// AVPacketQueue *audio_queue_ = NULL;
// AVPacketQueue *video_queue_ = NULL;
AVFormatContext *ifmt_ctx_ = NULL;
int audio_index_ = -1;
int video_index_ = -1;
};
#endif // DEMUXTHREAD_H
对上述代码中AVFormatContext进行解释:在使用FFMPEG进行开发的时候,AVFormatContext是一个贯穿始终的数据结构,很多函数都要用到它作为参数。它是FFMPEG解封装(flv,mp4,rmvb,avi)功能的结构体。下面看几个主要变量的作用(在这里考虑解码的情况):
struct AVInputFormat *iformat:输入数据的封装格式
AVIOContext *pb:输入数据的缓存
unsigned int nb_streams:视音频流的个数
AVStream **streams:视音频流
char filename[1024]:文件名
int64_t duration:时长(单位:微秒us,转换为秒需要除以1000000)
int bit_rate:比特率(单位bps,转换为kbps需要除以1000)
AVDictionary *metadata:元数据
demuxthread.cpp代码
#include "demuxthread.h"
#include "log.h"
DemuxThread::DemuxThread()
{
LogInfo("DemuxThread");
}
DemuxThread::~DemuxThread()
{
LogInfo("~DemuxThread");
if(thread_) {
Stop();
}
}
int DemuxThread::Init(const char *url)
{
LogInfo("url:%s", url);
int ret = 0;
url_ = url;
ifmt_ctx_ = avformat_alloc_context();//avformat_alloc_context
//主要完成AVFormatContext的空间分配,注意分配在堆上;
//给AVFormatContext的成员赋默认值;
//完成AVFormatContext内部使用对象AVFormatInternal结构体的空间分配及其部分成员字段的赋值。
ret = avformat_open_input(&ifmt_ctx_, url_.c_str(), NULL, NULL);//avformat_open_input()。该函数用于打开多媒体数据并且获得一些相关的信息。
if(ret < 0) {
av_strerror(ret, err2str, sizeof(err2str));
LogError("avformat_open_input failed, ret:%d, err2str:%s", ret, err2str);
return -1;
}
ret = avformat_find_stream_info(ifmt_ctx_, NULL);//1)该函数将读取媒体文件的音视频包去获取流信息。本函数常用于avformat_open_input()函数之后,在avformat_open_input()函数中会调用输入文件格式的read_header()函数,比如flv格式的flv_read_header()函数来读取文件头,由于flv格式的头很简单,只能知道是否存在音频流和视频流,获取不到流的编码信息,因此,对于flv格式来说,本函数就非常重要,本函数会读取flv文件中的音视频包,从这些包中获知流的编解码信息。对于没有文件头的MPEG格式来说存在同样的情况。
//2)本函数还会在MPEG的重复帧模式下计算真实的帧率。
//3)本函数不会改变文件的逻辑位置(即程序访问文件时的文件偏移offset),那些读取并用来做检测的数据包将被缓冲,并留作后续处理使用。
//4) 本函数的入参AVDictionary **options如果不为空,那么该入参是一个AVDictionary列表,第几个AVDictionary就作用于AVFormatContext.nb_streams的第几个流,如果在对应的流中找不到相应的选项,函数返回时,那么该入参中还会保留着没有找到的选项
//5)本函数不会保证打开所有的编解码器,因此,在函数返回时,选项非空是正常行为。
//6)本函数通过options来让用户决定哪些信息是需要的,因此,不浪费时间在获取一些用户根本需要的信息。
if(ret < 0) {
av_strerror(ret, err2str, sizeof(err2str));
LogError("avformat_find_stream_info failed, ret:%d, err2str:%s", ret, err2str);
return -1;
}
av_dump_format(ifmt_ctx_, 0, url_.c_str(), 0);
audio_index_ = av_find_best_stream(ifmt_ctx_, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);//去区分音频包和视频包,一般aduio_index_=0,video_index_=1,默认-1,即demuxthread.h代码中aduio_index_,video_index_都为-1。
video_index_ = av_find_best_stream(ifmt_ctx_, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
LogInfo("audio_index_:%d, video_index_:%d", audio_index_, video_index_);
if(audio_index_ < 0 || video_index_ < 0) {
LogError("no audio or no video");
return -1;
}
LogInfo("Init leave");
}
int DemuxThread::Start()
{
thread_ = new std::thread(&DemuxThread::Run, this);
if(!thread_) {
LogError("new std::thread(&DemuxThread::Run, this) failed");
return -1;
}
return 0;
}
int DemuxThread::Stop()
{
Thread::Stop();
avformat_close_input(&ifmt_ctx_);//该函数用于关闭一个AVFormatContext,一般情况下是和avformat_open_input()成对使用的。
}
void DemuxThread::Run()
{
LogInfo("Run into");
int ret = 0;
AVPacket pkt;//AVPacket是FFmpeg中很重要的一个数据结构,它保存了解复用(demuxer)之后,
//解码(decode)之前的数据(仍然是压缩后的数据)和关于这些数据的一些附加的信息,如显示时间戳(pts),
//解码时间戳(dts),数据时长(duration),所在流媒体的索引(stream_index)等等。
while (abort_ != 1) {
ret = av_read_frame(ifmt_ctx_, &pkt);
if(ret < 0) {
av_strerror(ret, err2str, sizeof(err2str));
LogError("av_read_frame failed, ret:%d, err2str:%s", ret, err2str);
break;
}
if(pkt.stream_index == audio_index_) {
LogInfo("audio pkt");
} else if(pkt.stream_index == video_index_) {
LogInfo("video pkt");
}
av_packet_unref(&pkt);//释放掉
}
LogInfo("Run finish");
}
二、包队列帧队列模块
queue.h代码
#ifndef QUEUE_H
#define QUEUE_H
#include <mutex>//#include<mutex>头文件中,所以如果你需要使用 std::mutex,就必须包含#include<mutex>头文件。
//C++11提供如下4种语义的互斥量(mutex) :
//std::mutex,独占的互斥量,不能递归使用。
//std::time_mutex,带超时的独占互斥量,不能递归使用。
//std::recursive_mutex,递归互斥量,不带超时功能。
//std::recursive_timed_mutex,带超时的递归互斥量。
#include <condition_variable>//头文件<condition_variable>
//条件变量是利用线程间共享的全局变量进行同步的一种机制,主要包括两个动作:一个线程等待
//条件变量的条件成立而挂起;另一个线程使条件成立(给出条件成立信号)。为了防止竞争,条件变
//量的使用总是和一个互斥量结合在一起。
//condition_variable
//condition_variable_any
//相同点:两者都能与std::mutex一起使用。
//不同点:前者仅限于与 std::mutex 一起工作,而后者可以和任何满足最低标准的互斥量一起工作,从而加上了_any的后缀。condition_variable_any会产生额外的开销。
//一般只推荐使用condition_variable。除非对灵活性有硬性要求,才会考虑condition_variable_any。
#include <queue>
template <typename T>
class Queue
{
public:
Queue() {}
~ Queue() {}
void Abort() {
abort_ = 1;
cond_.notify_all();//notify_one():因为只唤醒等待队列中的第一个线程;不存在锁争用,所以能够立即获得锁。其余的线程不会被唤醒,需要等待再次调用notify_one()或者notify_all()。
//notify_all():会唤醒所有等待队列中阻塞的线程,存在锁争用,只有一个线程能够获得锁。
}
int Push(T val) {
std::lock_guard<std::mutex> lock(mutex_);
if(1 == abort_) {
return -1;
}
queue_.push(val);
cond_.notify_one();
return 0;
}
int Pop(T &val, const int timeout = 0) {
std::unique_lock<std::mutex> lock(mutex_);//unique_lock是个类模板,工作中,一般lock_guard(推荐使用);lock_guard取代了mutex的lock()和unlock();
//unique_lock比lock_guard灵活很多,效率上差一点,内存占用多一点。
if(queue_.empty()) {
// 等待push或者超时唤醒
cond_.wait_for(lock, std::chrono::milliseconds(timeout), [this] {
return !queue_.empty() | abort_;
});
}
if(1 == abort_) {
return -1;
}
if(queue_.empty()) {
return -2;
}
val = queue_.front();
queue_.pop();
return 0;
}
int Front(T &val) {
std::lock_guard<std::mutex> lock(mutex_);//lock_guard构造互斥锁的写法,就是会在lock_guard构造函数里加锁,在析构函数里解锁,之所以搞了这个写法,C++委员会的解释是防止使用mutex加锁解锁的时候,忘记解锁unlock了。
if(1 == abort_) {
return -1;
}
if(queue_.empty()) {
return -2;
}
val = queue_.front();
return 0;
}
int Size() {
std::lock_guard<std::mutex> lock(mutex_);
return queue_.size();
}
private:
int abort_ = 0;//用于中止
std::mutex mutex_;
std::condition_variable cond_;
std::queue<T> queue_;
};
#endif // QUEUE_H
AVFrameQueue.h代码
#ifndef AVFRAMEQUEUE_H
#define AVFRAMEQUEUE_H
#include "queue.h"
#ifdef __cplusplus ///
extern "C"
{
// 包含ffmpeg头文件
//#include "libavutil/avutil.h"
//#include "libavformat/avformat.h"
#include "libavcodec/avcodec.h"
}
#endif
class AVFrameQueue
{
public:
AVFrameQueue();
~AVFrameQueue();
void Abort();
int Push(AVFrame *val);
AVFrame *Pop(const int timeout);
AVFrame *Front();
int Size();
private:
void release();
Queue<AVFrame *> queue_;
};
#endif // AVFRAMEQUEUE_H
AVFrameQueue.cpp代码
#include "avframequeue.h"
#include "log.h"
AVFrameQueue::AVFrameQueue()
{
}
AVFrameQueue::~AVFrameQueue()
{
}
void AVFrameQueue::Abort()
{
release();
queue_.Abort();
}
int AVFrameQueue::Push(AVFrame *val)
{
AVFrame *tmp_frame = av_frame_alloc();//释放帧和其中任何动态分配的对象,
// * 例如extended_data。如果帧被引用计数,则它将首先被取消引用。
av_frame_move_ref(tmp_frame, val);//为音频或视频数据分配新的缓冲区。
// * 在调用此函数之前,必须在帧上设置以下字段:
// * - 格式(视频的像素格式,音频的示例格式)
// * - 视频的宽度和高度
//* - 音频的nb_samples和channel_layout
// * 此函数将填充 AVFrame.data 和 AVFrame.buf 数组,如果
//必要,分配和填写AVFrame.extended_data和AVFrame.extended_buf。
//对于平面格式,将为每个平面分配一个缓冲区。
return queue_.Push(tmp_frame);
}
AVFrame *AVFrameQueue::Pop(const int timeout)
{
AVFrame *tmp_frame = NULL;
int ret = queue_.Pop(tmp_frame, timeout);
if(ret < 0) {
if(ret == -1)
LogError("AVFrameQueue::Pop failed");
}
return tmp_frame;
}
AVFrame *AVFrameQueue::Front()
{
AVFrame *tmp_frame = NULL;
int ret = queue_.Front(tmp_frame);
if(ret < 0) {
if(ret == -1)
LogError("AVFrameQueue::Pop failed");
}
return tmp_frame;
}
int AVFrameQueue::Size()
{
return queue_.Size();
}
void AVFrameQueue::release()
{
while (true) {
AVFrame *frame = NULL;
int ret = queue_.Pop(frame, 1);
if(ret < 0) {
break;
} else {
av_frame_free(&frame);//设置对源帧描述的数据的新引用。
// * 将帧属性从 src 复制到 dst,并为每个帧属性创建一个新引用 来自 src 的 AVBufferRef。
// * 如果 src 未被引用计数,则分配新的缓冲区并复制数据。
continue;
}
}
}
AVPacketQueue.h代码
#ifndef AVPACKETQUEUE_H
#define AVPACKETQUEUE_H
#include "queue.h"
#ifdef __cplusplus ///
extern "C"
{
// 包含ffmpeg头文件
//#include "libavutil/avutil.h"
//#include "libavformat/avformat.h"
#include "libavcodec/avcodec.h"
}
#endif
class AVPacketQueue
{
public:
AVPacketQueue();
~AVPacketQueue();
void Abort();
int Size();
int Push(AVPacket *val);
AVPacket *Pop(const int timeout);
private:
void release();
Queue<AVPacket *> queue_;
};
#endif // AVPACKETQUEUE_H
AVPacketQueue.cpp代码
#include "avpacketqueue.h"
#include "log.h"
AVPacketQueue::AVPacketQueue()
{
}
AVPacketQueue::~AVPacketQueue()
{
}
void AVPacketQueue::Abort()
{
release();
queue_.Abort();
}
int AVPacketQueue::Size()
{
queue_.Size();
}
int AVPacketQueue::Push(AVPacket *val)
{
AVPacket *tmp_pkt = av_packet_alloc();
av_packet_move_ref(tmp_pkt, val);
return queue_.Push(tmp_pkt);
}
AVPacket *AVPacketQueue::Pop(const int timeout)
{
AVPacket *tmp_pkt = NULL;
int ret = queue_.Pop(tmp_pkt, timeout);
if(ret < 0) {
if(ret == -1)
LogError("AVPacketQueue::Pop failed");
}
return tmp_pkt;
}
void AVPacketQueue::release()
{
while (true) {
AVPacket *packet = NULL;
int ret = queue_.Pop(packet, 1);
if(ret < 0) {
break;
} else {
av_packet_free(&packet);
continue;
}
}
}