接收HLS网络流并解析成h264及acc

工作需要开始接触流媒体内容,留个脚印;

流媒体转发服务中需要设计一个模块实现接收HLS网络流,解析出H264、aac格式音视频裸流发送到到AppServer模块转成rtmp\rtsp等流发送出去。这就需要先接收HLS网络流解HLS协议获取ts流,然后解复用ts流获取h264\aac。实际中HLS网络了的.m3u8文件中参数有差异,通过查找目前常见支持HLS流解析的有ffmpeg和VLC,VLC源码移植困难,选择ffmpeg库。

ffmpeg中支持HLS解协议部分参见博客:

  1. ffmpeg中hls.c代码分析
  2. 基于FFmpeg源码分析HLS拉流

附:两幅前辈大神梳理的ffmpeg函数调用关系图


解析代码:

调用ffmpeg库做HLS解协议不必纠结.m3u8内部不同,ffmpeg会自己探测,我们只需把它当作普通的rtsp流接收就行得到ts流。


    void TsDemuxer::DemuxerCycle() {
        while (GetRunStatus()) {

            int ret = 0;
            ifmt_ctx = NULL, ofmt_ctx = NULL;
            pb = NULL;
            ofmt = (AVOutputFormat*)av_mallocz(sizeof(AVOutputFormat));//输出格式

            int nVideoFramesNum = 0;
            uint8_t* buf = (uint8_t*)av_mallocz(sizeof(uint8_t)* 32768);

            int width = 0;
            int height = 0;
            int codeType = 0;
            int fpsDen = 0;
            int fpsNum = 0;

            video_index = -1;


            av_register_all();
            avformat_network_init();

            //打开输入流
            if ((ret = avformat_open_input(&ifmt_ctx, m_strFilePath.c_str(), 0, 0)) < 0)
            {
                log_error("Could not open input stream");
                break;
            }

            if ((ret = avformat_find_stream_info(ifmt_ctx, 0)) < 0)
            {
                log_error("Failed to retrieve input stream information");
                break;
            }

            //nb_streams代表有几路流,一般是2路:即音频和视频,顺序不一定
            for(int i=0;i<ifmt_ctx->nb_streams;i++){

                if(ifmt_ctx->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO)
                {
                    //这一路是视频流,标记一下,以后取视频流都从ifmt_ctx->streams[video_index]取
                    video_index=i;
                    break;
                }
            }

            av_dump_format(ifmt_ctx, 0, m_strFilePath.c_str(), 0);
            pb = avio_alloc_context(buf, 188, 1, this, NULL, WritePacket, NULL);
            avformat_alloc_output_context2(&ofmt_ctx, NULL, "mpegts", NULL/*out_filename*/);//设置输出视频的格式为TS格式,则设置为"mpegts"

            if (!ofmt_ctx) {
                log_error("Could not create output context\n");
                ret = AVERROR_UNKNOWN;
                goto end;
            }
            ofmt_ctx->pb = pb;//这个是关键,指定输出的方式
            ofmt_ctx->flags |= AVFMT_FLAG_CUSTOM_IO;
            log_debug("output format:%s[%s]\n", ofmt_ctx->oformat->name, ofmt_ctx->oformat->long_name);

            if(!ofmt_ctx)
            {
                log_error("Could not create output context\n");
                ret=AVERROR_UNKNOWN;
                goto end;
            }

            ofmt = ofmt_ctx->oformat;
            for(int i=0;i<ifmt_ctx->nb_streams;i++)
            {    //根据输入流创建输出流
                AVStream *in_stream = ifmt_ctx->streams[i];
                AVStream *out_stream = avformat_new_stream(ofmt_ctx,in_stream->codec->codec);
                if(!out_stream)
                {
                    log_error("Failed allocating output stream.\n");
                    ret = AVERROR_UNKNOWN;
                    goto end;
                }

                //将输出流的编码信息复制到输入流
                ret = avcodec_copy_context(out_stream->codec,in_stream->codec);
                if(ret<0)
                {
                    log_error("Failed to copy context from input to output stream codec context\n");
                    goto end;
                }
                out_stream->codec->codec_tag = 0;

                if(ofmt_ctx->oformat->flags & AVFMT_GLOBALHEADER)
                    out_stream->codec->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;

            }
            av_dump_format(ofmt_ctx,0,NULL,1);

            ofmt_ctx->streams[0]->codec->codec_type = AVMEDIA_TYPE_VIDEO;
            ofmt_ctx->streams[0]->codec->width = ifmt_ctx->streams[video_index]->codec->width;
            ofmt_ctx->streams[0]->codec->height = ifmt_ctx->streams[video_index]->codec->height;
            ofmt_ctx->streams[0]->codec->time_base.num = ifmt_ctx->streams[video_index]->codec->time_base.num;
            ofmt_ctx->streams[0]->codec->time_base.den = ifmt_ctx->streams[video_index]->codec->time_base.den;
            ofmt_ctx->streams[0]->codec->codec_id = AV_CODEC_ID_H264;
            ret = avformat_write_header(ofmt_ctx,NULL);
            if(ret < 0)
            {
                log_error("Error occured when opening output URL\n");
                goto end;
            }

            //实现REMUXING
            while (1) {
                AVStream *in_stream, *out_stream;
                ret = av_read_frame(ifmt_ctx, &pkt);
                if (ret < 0)
                    break;
                in_stream = ifmt_ctx->streams[pkt.stream_index];
                out_stream = ofmt_ctx->streams[pkt.stream_index];

                if(in_stream->codec->codec_type == AVMEDIA_TYPE_VIDEO)
                {
                    if(pkt.pts == AV_NOPTS_VALUE) //FIX:No PTS (Example: Raw H.264)
                    {
                        AVRational time_base1 = out_stream->time_base;

                        //Duration between 2 frames (us)
                        int64_t calc_duration =(double)AV_TIME_BASE/av_q2d(in_stream->r_frame_rate);

                        pkt.pts = (double)(nVideoFramesNum*calc_duration)/(double)(av_q2d(time_base1)*AV_TIME_BASE);
                        pkt.dts = pkt.pts;
                        pkt.duration = (double)calc_duration/(double)(av_q2d(time_base1)*AV_TIME_BASE);
                    }
                    else
                    {
                        pkt.pts = av_rescale_q_rnd(pkt.pts, in_stream->time_base, out_stream->time_base, (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
                        pkt.dts = av_rescale_q_rnd(pkt.dts, in_stream->time_base, out_stream->time_base, (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
                        pkt.duration = av_rescale_q(pkt.duration, in_stream->time_base, out_stream->time_base);
                        pkt.pos = -1;
                    }

                    nVideoFramesNum++;
                }
                else
                {
                    pkt.pts = av_rescale_q_rnd(pkt.pts, in_stream->time_base, out_stream->time_base, (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
                    pkt.dts = av_rescale_q_rnd(pkt.dts, in_stream->time_base, out_stream->time_base, (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
                    pkt.duration = av_rescale_q(pkt.duration, in_stream->time_base, out_stream->time_base);
                    pkt.pos = -1;
                }


                ret = av_interleaved_write_frame(ofmt_ctx, &pkt);
                if (ret < 0) {
                    log_error("Error muxing packet\n");
                    break;
                }
                av_free_packet(&pkt);
            }
            av_write_trailer(ofmt_ctx);

            end:
            break;

        }
    }
}

 上面代码由于本人接收的设备只有视频流,所以这里去掉了音频;

这里最重要的一个函数是:

pb = avio_alloc_context(buf, 188, 1, this, NULL, WritePacket, NULL);

WritePacket是个结果回调函数,得到的就是ts流,第三个参数设置为1进入回调,第二个参数为是数据缓冲空间,满多少字节进入一次回调,这里设置为188是为ts文件解复用所用,ts解复用协议中规定了单次输入字节为188,不然回调没作用。

输出ts流的回调函数WritePacket:

    int WritePacket(void *opaque, uint8_t *buf, int buf_size){
        TsDemuxer *p = (TsDemuxer*) opaque;

        size_t ret =  ts_demuxer_input((ts_demuxer_t*)p->ts, buf, buf_size);
        if (ret != 0 ){
            log_error("ts demuxer input error");
        }

        return 0;
    }

ts文件解复用:

size_t ret =  ts_demuxer_input((ts_demuxer_t*)p->ts, buf, buf_size);

这个函数就是ts文件解复用的输入,同样输出的h264,acc是通过回调函数形式,这里需要注意的是,如果需要ts解复用就需要在所有函数之前创建上下文,本人把创建上下文函数放在了类的构造函数中。

ts = ts_demuxer_create(onTsPacket, this);

onTsPacket就是回到函数(上下文);

    void onTsPacket(void* param, int stream, int codec_id, int flags, int64_t pts, int64_t dts, const void* data, size_t bytes)
    {

        TsDemuxer *p = (TsDemuxer*) param;

        //todo 获取了裸流数据信息,可以变魔术了
    }

onTsPacket函数获取的就是裸流信息,可以拿去做处理了,注意这个函数获得数据做后续操作时我们通常需要帧率,帧的类型,针对类型不能用flags判断,这里协议里实际是指帧对齐标志,自己写一个判断函数好了就几行。这个函数每次输出的是一帧的数据,也就是说这个函数调用频率就是裸流帧率,本人的HLS输入流帧率是25,输出的H264是20,使用时可以在onTsPacket函数放个累加测测看。(坑:后续操作如果遇见问题,可以在这个函数中把单帧数据写文件分析一下,一般都是帧类型判断错误,或者帧率不对导致)

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值