直播卖货系统开发实现直播的重要过程——推流

VC环境集成FFMPEG

直播卖货系统开发想要顺利的实现直播,就需要进行推流,接下来就让我们一起了解一下,直播卖货系统开发如何通过FFMPEG实现简单的推流。

  • 使用Visual Studio创建一个win32项目,并在解决方案文件的目录下,创建FMPEG文件夹
  • 从官网下载,将dev版本中的include文件夹和bin文件夹拷贝到上面创建的FMPEG文件夹内
  • 在项目--属性--配置属性--C/C++--常规--附加包含目录中添加“../FFMPEG/include”
  • 在项目--属性--配置属性--链接器--常规--附加目录库中添加“../FFMPEG/lib”
  • 在项目--属性--配置属性--链接器--输入--附加依赖项中添加lib的文件名:avcodec.lib;avdevice.lib;avfilter.lib;avformat.lib;avutil.lib;postproc.lib;swresample.lib;swscale.lib;
  • 将share版本中bin目录下的dll拷贝到解决方案的Debug目录下
  • 将一个flv文件拷贝到Debug目录下

FFMPEG相关概念

  • 为什么会有DTS解码时间戳和PTS显示时间戳
    通常一个视频的视频帧是这样的顺序来播放的,IBBP,但是由于算法的原因,可能在展示B帧的时候,需要用到P帧的数据,因此实际上的存储顺序可能是IPBB,所以对于帧而言,他的解码顺序与播放顺序并不一致。如果直播卖货系统开发,仅仅使用简单的帧数乘以频率的方式来同步视频,播放端在改变了播放速度或者播放时间的时候,就无法正确同步了
  • time_base的作用
    在视频内没有绝对时间,只有相对时间,如00:00:05是指相对于起始点来讲的,在视频内部使用时间戳来表示,时间戳是按照时间刻度来计算的,也就是time_base,如果time_base是1/60,就表示一个时间单位是1/60秒。如果直播卖货系统开发在编码的时候采用了1/1000的time_base,某个帧的pts是465000,在视频解码时,换成了1/9000的time_base,此时时间刻度不一致了,就需要通过计算才能得到解码时的pts
  • AVRational
typedef struct AVRational{
    int num; ///< Numerator
    int den; ///< Denominator
} AVRational;

说白了就是标识一个分数,num是分子,den是分母,用av_q2d来表示这个分数的double结果

一个RTMP推流的代码

#include <iostream>

extern "C"
{
#include "libavformat/avformat.h"
#include "libavutil/mathematics.h"
#include "libavutil/time.h"
};

int avError(int errNum);

int main(int argc, char* argv[]){
    //输入文件
    const char *fileAddress = "cuc_ieschool.flv";
    //推流地址
    const char *rtmpAddress = "rtmp://video-center.alivecdn.com/record/1513?vhost=im-broadcast17.sdp.101.com&auth_key=1843197296-0-0-f6bac834f49cce39c3fd5f3b7ec0bcb1";

    //注册所有库
    av_register_all();
    //初始化网络库
    avformat_network_init();


    
    //                       输入流处理部分                      ///
    
    AVFormatContext *ictx = NULL;
    //打开文件
    int ret = avformat_open_input(&ictx, fileAddress, 0, NULL);
    if (ret < 0)
    {
        return avError(ret);
    }
    std::cout << "avformat_open_input succeeded" << std::endl;
    //获取流信息
    ret = avformat_find_stream_info(ictx, 0);
    if (ret != 0)
    {
        return avError(ret);
    }
    //打印视频信息
    av_dump_format(ictx, 0, fileAddress, 0);


    
    //                       输出流处理部分                      ///
    
    AVFormatContext *octx = NULL;
    //创建输出上下文
    ret = avformat_alloc_output_context2(&octx, NULL, "flv", rtmpAddress); 
    if (ret < 0) {
        return avError(ret);
    }
    std::cout << "avformat_alloc_output_context2 succeeded" << std::endl;
    //配置输出流
    for (int i = 0; i < ictx->nb_streams; i++) {
        //创建一个新的流
        AVStream *outStream = avformat_new_stream(octx, ictx->streams[i]->codec->codec);
        if (!outStream) {
            return avError(0);
        }
        //复制配置信息
        ret = avcodec_parameters_copy(outStream->codecpar, ictx->streams[i]->codecpar);
        if (ret < 0) {
            return avError(ret);
        }
        outStream->codec->codec_tag = 0;
    }
    //打印输出流的信息
    av_dump_format(octx, 0, rtmpAddress, 1);


    
    //                         准备推流                          ///
    
    //打开io
    ret = avio_open(&octx->pb, rtmpAddress, AVIO_FLAG_WRITE);
    if (ret < 0) {
        avError(ret);
    }
    //写入头部信息
    ret = avformat_write_header(octx, NULL);
    if ( ret < 0) {
        avError(ret);
    }
    std::cout << "avformat_write_header succeeded" << std::endl;
    //推流每一帧数据
    AVPacket avPacket;
    long long startTime = av_gettime();
    while (true)
    {
        ret = av_read_frame(ictx, &avPacket);
        if (ret < 0 )
        {
            break;
        }
        std::cout << avPacket.pts << " " << std::flush;
        //计算转换时间戳
        //获取时间基数
        AVRational itime = ictx->streams[avPacket.stream_index]->time_base;
        AVRational otime = octx->streams[avPacket.stream_index]->time_base;
        avPacket.pts = av_rescale_q_rnd(avPacket.pts, itime, otime, (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_NEAR_INF));
        avPacket.dts = av_rescale_q_rnd(avPacket.dts, itime, otime, (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_NEAR_INF));
        //到这一帧经历了多长时间
        avPacket.duration = av_rescale_q_rnd(avPacket.duration, itime, otime, (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_NEAR_INF));
        avPacket.pos = -1;
        //视频帧推送速度
        if (ictx->streams[avPacket.stream_index]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
        {
            AVRational tb = ictx->streams[avPacket.stream_index]->time_base;
            //已经过去的时间
            long long now = av_gettime() - startTime;
            long long dts = 0;
            dts = avPacket.dts * (1000 * 1000 * av_q2d(tb));
            if (dts > now)
            {
                av_usleep(dts - now);
            }
        }
        ret = av_interleaved_write_frame(octx, &avPacket);
        if (ret < 0)
        {
            break;
        }
    }
    

    std::cin.get();
    return 0;
}

int avError(int errNum) {
    char buf[1024];
    //获取错误信息
    av_strerror(errNum, buf, sizeof(buf));
    std::cout << " failed! " << buf << std::endl;
    return -1;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值