ffmpeg播放器项目学习

框架
在这里插入图片描述

一、解复用模块

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;
        }
    }
}

三、解码线程实现

在这里插入图片描述

  • 20
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值