首页 > 编程语言 >zlmediakit源码学习(扩展支持转码H265/H264)

zlmediakit源码学习(扩展支持转码H265/H264)

时间:2023-08-18 13:33:57浏览次数:49  
标签:std const 转码 track frame 源码 video zlmediakit Ptr

在zlmediakit源码基础上继续探索扩展支持H265/H264的转码功能。参照上一篇帖子:https://www.cnblogs.com/feixiang-energy/p/17623567.html

 

作者已经封装好了基于ffmpeg实现的解码、编码、视频图像格式转换、音频重采样等接口,https://gitee.com/xia-chu/ZLMediaKit/blob/feature/transcode/src/Codec/Transcode.h 。在参考了zlemdiakit的feature/transcode分支的基础上,不适用外挂ffmpeg命令,而是在代码中进行实现转码。

------------------------------------------------

关键代码如下:

1.在FFmpegTranscoder.h中增加一个用于发布转码后视频的MediaSource:_muxerServer
#ifndef FFMPEGTRANSCODER_H_
#define FFMPEGTRANSCODER_H_
#include <mutex>
#include <memory>
#include "Util/util.h"
#include "Util/logger.h"
#include "Util/TimeTicker.h"
#include "Util/TimeTicker.h"
#include "Common/MediaSink.h"
#include "Transcoder.h"
#include "FFmpegMuxer.h"
#include "Common/MultiMediaSourceMuxer.h"
namespace mediakit {
class FFmpegTranscoder: public MediaSinkInterface, public MediaSourceEvent {
public:
    using Ptr = std::shared_ptr<FFmpegTranscoder>;
    FFmpegTranscoder(
        const std::string &path, const std::string &vhost, const std::string &app,  const std::string &stream_id,
        size_t max_second);
    ~FFmpegTranscoder() override;
    /**
     * 重置所有Track
     */
    void resetTracks() override;
    /**
     * 输入frame
     */
    bool inputFrame(const Frame::Ptr &frame) override;
    /**
     * 添加ready状态的track
     */
    bool addTrack(const Track::Ptr &track) override;
    bool onNewFrame(const Frame::Ptr &frame);
private:
    void stop();
    void asyncStop();
private:
    bool _have_video = false;
    size_t _max_second;
    std::string _folder_path;
    std::string _full_path;
    std::string _full_path_tmp;
    TranscodeInfo _info;
    FFmpegMuxer::Ptr _muxer;
    std::list<Track::Ptr> _tracks;
    MultiMediaSourceMuxer::Ptr _muxerServer;
};
} // namespace mediakit
#endif /* FFMPEGTRANSCODER_H_ */

2.在FFmpegTranscoder的构造函数中定义_muxerServer。

FFmpegTranscoder::FFmpegTranscoder(const std::string &path, const std::string  &vhost, const std::string &app, const std::string &stream_id,size_t max_second) {
    _folder_path = path;
    /////record 业务逻辑//////
    _info.app = app;
    _info.stream = stream_id;
    _info.vhost = vhost;
    _info.folder = path;
    GET_CONFIG(size_t, recordSec, Record::kFileSecond);
    _max_second = max_second ? max_second : recordSec;
    _muxer = std::make_shared<FFmpegMuxer>(_folder_path, _max_second);
    ProtocolOption option;
    //读取mp4文件并流化时,不重复生成mp4/hls文件
    option.enable_mp4 = false;
    option.enable_hls = false;
    _muxerServer = std::make_shared<MultiMediaSourceMuxer>(vhost, app, stream_id +  "_new", 0.0, option);
}

3.FFmpegTranscoder::addTrack。向转码对象添加轨道的时候也向_muxerServer中添加轨道。暂时先只关注视频轨道

bool FFmpegTranscoder::addTrack(const Track::Ptr &track) {
    //保存所有的track,为创建MP4MuxerFile做准备
    _tracks.emplace_back(track);
    if (track->getTrackType() == TrackVideo) {
        _have_video = true;
        _muxer->addTrack(track);
        //创建新的视频轨道,并添加到发布对象中
        H264Track::Ptr newTrack(new H264Track());
        VideoTrack::Ptr video = static_pointer_cast<VideoTrack>(track);
        newTrack->setVideoWidth(video->getVideoWidth());
        newTrack->setVideoHeight(video->getVideoHeight());
        newTrack->setBitRate(video->getBitRate());
        _muxerServer->addTrack(newTrack);
        _muxerServer->addTrackCompleted();
        //设置封装器的帧回调函数,即将收到重新编码之后的视频帧写入到发布对象中
        _muxer->setOnFrame([this](const Frame::Ptr &frame) {
            _muxerServer->inputFrame(frame);
        });
    }
    return true;
}

4.在FFmpegMuxer.h中定义一个转码之后帧数据的回调std::function<void(const Frame::Ptr &)>

#include "Common/MediaSink.h"
#include "Codec/Transcode.h"
namespace mediakit {
class FFmpegMuxer : public MediaSinkInterface {
public:
    using onFrame = std::function<void(const Frame::Ptr &)>;
    typedef std::shared_ptr<FFmpegMuxer> Ptr;
   
    FFmpegMuxer(std::string filePath = "", int gapTime = 1);
    ~FFmpegMuxer() override;
        /**
     * 添加已经ready状态的track
     */
    bool addTrack(const Track::Ptr &track) override;
    /**
     * 输入帧
     */
    bool inputFrame(const Frame::Ptr &frame) override;
    /**
     * 重置所有track
     */
    void resetTracks() override;
    /**
     * 是否包含视频
     */
    bool haveVideo() const;
    int frameToImage(AVFrame *frame, enum AVCodecID codecID, uint8_t *outbuf,  size_t outbufSize);
    void stop();
    void setOnFrame(onFrame cb);
private:
    std::shared_ptr<FFmpegDecoder> _audio_dec;
    std::shared_ptr<FFmpegDecoder> _video_dec;
    std::shared_ptr<FFmpegEncoder> _audio_enc;
    std::shared_ptr<FFmpegEncoder> _video_enc;
    int _index;
    time_t _last_time;
    std::string _folder_path;
    int _gapTime;
    onFrame _cb;
};
}

5.FFmpegMuxer::addTrack。修改addTrack函数。设置解码器、编码器的回调函数

bool FFmpegMuxer::addTrack(const Track::Ptr &track) {
    if (track->getTrackType() == TrackVideo) {
        _video_dec.reset(new FFmpegDecoder(track));
        // 创建新的H264视频帧轨道,添加到编码器中
        H264Track::Ptr newTrack(new H264Track());
        VideoTrack::Ptr video = static_pointer_cast<VideoTrack>(track);
        newTrack->setVideoWidth(video->getVideoWidth());
        newTrack->setVideoHeight(video->getVideoHeight());
        newTrack->setBitRate(video->getBitRate());
        _video_enc.reset(new FFmpegEncoder(newTrack));
        // 设置视频编码器的回调函数,即接收到重新编码成H264的视频帧之后回调给上层,最终写入到FFmpegTranscoder的_muxerServer中
        _video_enc->setOnEncode([this](const Frame::Ptr &frame) {
            if (_cb) {
                _cb(frame);
            }
        });
        
        // 设置视频解码器的回调函数,即接收到解码之后的YUV图像之后写入到视频编码器中进行重新编码
        _video_dec->setOnDecode([this](const FFmpegFrame::Ptr &frame) {
            // 转码操作
            _video_enc->inputFrame(frame, false);
            /*
            // --- 抽帧操作 begin
            time_t now = ::time(NULL);
            if (now - _last_time >= _gapTime) {
                AVFrame *avFrame = frame->get();
                int bufSize = av_image_get_buffer_size(AV_PIX_FMT_BGRA,  avFrame->width, avFrame->height, 64);
                uint8_t *buf = (uint8_t *)av_malloc(bufSize);
                int picSize = frameToImage(avFrame, AV_CODEC_ID_MJPEG, buf,  bufSize);
                if (picSize > 0) {
                    auto file_path = _folder_path + getTimeStr("%H-%M-%S_") +  std::to_string(_index) + ".jpeg";
                    auto f = fopen(file_path.c_str(), "wb+");
                    if (f) {
                        fwrite(buf, sizeof(uint8_t), bufSize, f);
                        fclose(f);
                    }
                }
                av_free(buf);
                _index++;
                _last_time = now;
            }
            // --- 抽帧操作 end
            */
        });
    }
    return true;
}
6.FFmpegMuxer::inputFrame。接收到H265视频帧之后写入到解码器进行解码   
bool FFmpegMuxer::inputFrame(const Frame::Ptr &frame) {
    if(frame->getTrackType() == TrackVideo) {
        if (frame->getCodecId() == CodecH265 /* || frame->getCodecId() ==  CodecH264*/) {
            if (_video_dec != nullptr) {
                _video_dec->inputFrame(frame, true, false, false);
            }
        }
    }
    return true;
}

7.转码效果:

    1)原始的RTSP视频流:H265     2)zlmediakit转流之后的/live/test.live.flv视频流:H265     3)启动转码之后新的/live/test_new.live.flv视频流:H264  

标签:std,const,转码,track,frame,源码,video,zlmediakit,Ptr
From: https://www.cnblogs.com/feixiang-energy/p/17640249.html

相关文章

  • 医疗信息云LIS系统源码 .Net Core版SaaS模式
    SaaS模式.NetCore版云LIS系统源码医疗信息LIS系统是专为医院检验科设计的一套实验室信息管理系统,能将实验仪器与计算机组成网络,使病人样品登录、实验数据存取、报告审核、打印分发,实验数据统计分析等繁杂的操作过程实现了智能化、自动化和规范化管理。有助于提高实验室的整体管理......
  • 无限开商户的开源SaaS进销存源码,商户支持多门店多仓库
    随着软件行业的发展,越来越多的公司开始采用SaaS(软件即服务)模式来构建软件产品,以降低部署和维护的成本,并方便软件升级和更新。SaaS进销存系统是一个基于云计算的管理系统,通过浏览器或移动设备访问,无需在本地安装大量软件。它具有轻量级、自动化等特点,可以快速上手,降低企业成本。管店......
  • 国产麒麟系统KylinOS Server V10 SP2安装MySQL 8.0.26—源码编译安装
    一:操作系统环境检查1.1首先确认操作系统版本是KylinOSServerV10SP2麒麟操作系统KylinosServerV10SP2使用的安装介质是Kylin-Server-10-SP2-x86-Release-Build09-20210524.iso,执行以下命令查看版本:cat/etc/kylin-releasecat/proc/version 1.2检查系统是否......
  • 直播系统源码协议探索篇(二):网络套接字协议WebSocket
     上一篇我们分析了直播平台的会话初始化协议SIP,他关乎着直播平台的实时通信和多方互动技术的实现,今天我们来讲另一个协议,叫网络套接字协议WebSocket,WebSocket基于TCP在客户端与服务器建立双向通信的网络协议,并且可以通过单个长连接实现。在直播系统源码平台已经成为人们获取知识......
  • CentOS9中的Glibc2.17源码编译升级到Glibc2.31
    一、准备工作1、配置yum阿里镜像源查看yum当前配置的仓库,如果yum配置的不是阿里云源,请配置阿里云源。yumrepolistall验证是否能ping通阿里云#如果不能ping通可能是DNS没有配置pingmirrors.aliyun.com备份官方的原yum源配置mv/etc/yum.repos.d/CentOS-Base.r......
  • 直播系统源码协议探索篇(二):网络套接字协议WebSocket
    上一篇我们分析了直播平台的会话初始化协议SIP,他关乎着直播平台的实时通信和多方互动技术的实现,今天我们来讲另一个协议,叫网络套接字协议WebSocket,WebSocket基于TCP在客户端与服务器建立双向通信的网络协议,并且可以通过单个长连接实现。在直播系统源码平台已经成为人们获取知识、放......
  • Spring源码学习笔记13——总结篇, 从IOC到AOP
    系列文章目录和关于我零丶序言在《Spring源码学习笔记12——总结篇,IOC,Bean的生命周期,三大扩展点》中,我们总结了SpringIOC部分的知识,为了更好的给群里的伙伴们分享SpringAOP的知识,遂有了这篇文章,这篇文章将从IOC聊到AOP,其中IOC不会那么细致,重点还是在AOP。一丶引入1.AOP概述......
  • 外卖订餐系统源码:数字化餐饮新篇章
    在当今数字化时代,外卖订餐系统源码成为餐饮行业的一颗明星,为餐厅和顾客提供了无与伦比的便捷体验。在本文中,我们将一起探索一个简单的外卖订餐系统源码示例,了解它是如何将美食带到您的门口的。#导入所需模块importtime#定义餐厅菜单restaurant_menu={"汉堡":25,......
  • 社交软件源码的核心,IM即时通讯技术
    即时通讯(InstantMessaging)在社交软件源码中有着实时沟通和传输信息技术等服务,比方说我们大家熟悉的微信,QQ等聊天软件,目前,IM技术不仅仅活跃在社交场景中,还在电商直播等各种场景有所表现,一个成熟的IM产品落地,大体上能够分成三个重要部分:客户端开发,服务端开发,服务运维。IM技术在社交......
  • 社交软件源码的核心,IM即时通讯技术
      即时通讯(InstantMessaging)在社交软件源码中有着实时沟通和传输信息技术等服务,比方说我们大家熟悉的微信,QQ等聊天软件,目前,IM技术不仅仅活跃在社交场景中,还在电商直播等各种场景有所表现,一个成熟的IM产品落地,大体上能够分成三个重要部分:客户端开发,服务端开发,服务运维。 ......