FFmpeg安卓流媒体播放项目05 : IDecode和FFDecode模块代码创建

音视频解码接口类 :IDecode

#include "XParameter.h"
#include "IObserver.h"
#include <list>

//解码接口, 支持硬解码
class IDecode : public IObserver {
public:
    //打开解码器
    virtual bool open(XParameter parameter) = 0;

    //future模式 发送数据到线程解码
    virtual bool sendPacket(XData pkt) = 0;

    //从线程中获取解码结果, 并不会阻塞, 再次调用会复用上次空间, 线程不安全
    virtual XData recvFrame() = 0;

    //由主体notift的数据 达到最大队列缓冲则阻塞
    virtual void update(XData xData);

    bool isAudio = false;

    //最大的队列缓冲
    int maxList = 100;
protected:
    virtual void main();
    //读取缓冲
    std::list<XData> xDataList;
    std::mutex xDataListMutex;
};

#endif //BOPLAY_IDECODE_H
  • IDecode.cpp
#include "IDecode.h"
#include "XLog.h"

void IDecode::main() {
    while(!isExit){
        xDataListMutex.lock();
        if (xDataList.empty()){
            xDataListMutex.unlock();
            XSleep(1);
            continue;
        }
        //取出packet 消费者
        XData xData = xDataList.front();
        xDataList.pop_front();

        //开始解码
        //发送数据到解码线程, 音频一个数据包可能解码多个结果
        if(sendPacket(xData)){
            while(!isExit){
                //获取解码数据
                XData frame = recvFrame();
                if (!frame.data){
                    break;
                }
                //发送数据给观察者,解码模块得观察者是音频重采样模块IResample或视频渲染模块IVideoView
                this->Notify(frame);
            }
        }
        //因为这是从xDataList取出的?
        xData.Drop();
        xDataListMutex.unlock();
    }

}

void IDecode::update(XData xData) {
    if (xData.isAudio != isAudio){
        return;
    }
    while(!isExit){
        xDataListMutex.lock();
        if (xDataList.size() < maxList){
            //生产者
            xDataList.push_back(xData);
            xDataListMutex.unlock();
            break;
        }
        xDataListMutex.unlock();
        //休眠1毫秒防止消耗资源
        XSleep(1);
    }
}

音视频解码适配器类 :FFDecode

  • 编码器初始化函数
    主要流程:1、查找解码器;2、创建编码器上下文,并复制参数;3、打开解码器
bool FFDecode::open(XParameter parameter, bool isHard) {
    //在重新打开时优先释放掉上次打开的资源,
    close();
    if(nullptr == parameter.para){
        return false;
    }
    //解码器参数
    AVCodecParameters *para = parameter.para;
    //1.查找解码器
    //AVCodec是存储编解码器信息的结构体
    AVCodec *avc = avcodec_find_decoder(para->codec_id);
    if (isHard){
        //如果移植到其他平台代码要做调整
        avc = avcodec_find_decoder_by_name("h264_mediacodec");
    }

    if(!avc){
        XLOGI("avcodec_find_decoder %d failed! %d", para->codec_id, isHard);
    }
    XLOGI("avcodec_find_decoder %d successfully! %d", para->codec_id, isHard);
    //2.创建解码器上下文, 并复制参数
    std::unique_lock<std::mutex> lock{codecContextMutex};
    //AVCodecContex结构体除了包含解码器之外还包含视音频流相关的信息,如宽高,比特率,声道数等信息
    codecContext = avcodec_alloc_context3(avc);
    //参数 AVCodecParameters为编解码器的相关参数,是从AVCodecContext分离出来,其结构体中没有函数
    avcodec_parameters_to_context(codecContext, para);

    //多线程解码
    codecContext->thread_count = 8;
    //3.打开解码器
    //该函数用于初始化一个视音频编解码器的AVCodecContext
    int re = avcodec_open2(codecContext, nullptr, nullptr);
    if (re != 0){
        char buf[1024] = {0};
        av_strerror(re, buf, sizeof(buf) - 1);
        XLOGE("%s", buf);
        return false;
    }
    XLOGI("avcodec_open2 successfully!");

    if (AVMEDIA_TYPE_VIDEO == codecContext->codec_type){
        this->isAudio = false;
    } else if (AVMEDIA_TYPE_AUDIO == codecContext->codec_type){
        this->isAudio = true;
    }
    return true;
}
  • 发送AVPacket编码数据包到ffmpeg的解码线程
bool FFDecode::sendPacket(XData xData) {
    //临时变量
    if (xData.size <= 0 || !xData.data){
        return false;
    }

    //codecContext类成员, 多线程访问的变量
    std::unique_lock<std::mutex> lock(codecContextMutex);
    if (!codecContext){
        return false;
    }

    int re = avcodec_send_packet(codecContext, (AVPacket *)xData.data);
    return re == 0;
}
  • 接收ffmpeg解码后的帧数据
XData FFDecode::recvFrame() {
    std::unique_lock<std::mutex> lock(codecContextMutex);
    if (!codecContext){
        return XData{};
    }
    if (!frame){
        frame = av_frame_alloc();
    }
    //再次调用frame会复用上次空间, 线程不安全
    int re = avcodec_receive_frame(codecContext, frame);
    if (re != 0){
        return XData{};
    }
    XData xData;
    xData.data = (unsigned char*)frame;
    if (AVMEDIA_TYPE_VIDEO == codecContext->codec_type){
        //视频帧大小,帧宽,帧高
        xData.size = (frame->linesize[0] + frame->linesize[1] + frame->linesize[2]) * frame->height;
        xData.width = frame->width;
        xData.height = frame->height;
    }else if (AVMEDIA_TYPE_AUDIO == codecContext->codec_type){
        //音频帧大小 = 样本字节数 * 单通道样本数 * 通道数
        xData.size = av_get_bytes_per_sample((AVSampleFormat)frame->format) * frame->nb_samples * frame->channels;
    }
    //遇到问题记录, 这边把xData.datas写成xData.data导致uv数据没有复制, 只有y, 有图像运行但是是绿色的
    xData.format = frame->format;
    memcpy(xData.datas, frame->data, sizeof(xData.datas));
    xData.pts = (int)frame->pts;
    pts = xData.pts;
    return xData;
}
  • 硬解码初始化
//硬解码初始化
//https://blog.csdn.net/zhangpengzp/article/details/88943867
void FFDecode::initHard(void *vm) {
    av_jni_set_java_vm(vm, nullptr);
}
  • 关闭解码模块
void FFDecode::close() {
    //清空AVPacket缓冲
    IDecode::clear();
    codecContextMutex.lock();
    pts = 0;
    if (frame){
        av_frame_free(&frame);
    }
    if (codecContext){
        avcodec_close(codecContext);
        avcodec_free_context(&codecContext);
    }
    codecContextMutex.unlock();
}
  • 丢掉解码器中缓存帧
void FFDecode::clear() {
    IDecode::clear();
    mux.lock();
    //丢掉解码器中缓存帧
    if (codecContext){
        avcodec_flush_buffers(codecContext);
    }
    mux.unlock();
}
  • FFDecode.h
#ifndef BOPLAY_FFDECODE_H
#define BOPLAY_FFDECODE_H

#include "IDecode.h"

struct AVCodecContext;
struct AVFrame;

class FFDecode : public IDecode {
public:
    static void initHard(void *vm);

    virtual bool open(XParameter parameter, bool isHard = false);
    virtual void close();

    //future模型 发送数据到线程解码
    virtual bool sendPacket(XData pkt);

    //从线程中获取解码结果, 并不会阻塞, 再次调用会复用上次空间, 线程不安全
    virtual XData recvFrame();

    virtual void clear();

protected:
    AVCodecContext *codecContext = 0;
    AVFrame *frame = 0;
    std::mutex codecContextMutex;
};


#endif //BOPLAY_FFDECODE_H
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342