一次ffmpeg精简编译引发的推流失败

2021-04-16  本文已影响0人  叶迎宪

因为工作方面的需要,想单独编译出一个迷你的ffmpeg,仅用于支持rtsp至rtmp的协议转换。一开始采用的编译配置为

./configure --disable-everything --disable-x86asm --enable-protocol=rtmp --enable-demuxer=rtsp --enable-parser=h264 --enable-muxer=flv

转协议命令为

./ffmpeg -rtsp_transport tcp -i rtsp://xx.xx.xx.xx/camera_live -c:v copy -c:a copy -f flv rtmp://xx.xx.xx.xx/live/test1

结果转码报错
[NULL @ 0x1217b80] non-existing PPS 0 referenced
Last message repeated 195 times
[rtsp @ 0x1214500] Stream #0: not enough frames to estimate rate; consider increasing probesize
[rtsp @ 0x1214500] Could not find codec parameters for stream 0 (Video: h264 (High), none(progressive)): unspecified size
Consider increasing the value for the 'analyzeduration' and 'probesize' options

因为这个rtsp流在sdp中没有填写sps、pps,因此ffmpeg解复用的时候获取不到流的参数信息。而且又没有带上解码器,也无法通过解码获取码流参数。因此加上h264解码器再编译一次

./configure --disable-everything --disable-x86asm --enable-protocol=rtmp --enable-demuxer=rtsp --enable-parser=h264 --enable-decoder=h264 --enable-muxer=flv

这样改了之后,虽然可以正确获取到视频流的参数,但是在连接到srs服务器后,立马被断开了。ffmpeg的报错信息为
av_interleaved_write_frame(): Connection reset by peer
[flv @ 0xa2f880] Failed to update header with correct duration.
[flv @ 0xa2f880] Failed to update header with correct filesize.
Error writing trailer of rtmp://192.168.1.47:18092/live/hikvis: Connection reset by peer

而srs的报错信息为
[2021-04-16 18:03:06.850][Error][cycle][22026][fh669396][4] serve error code=3001 : service cycle : rtmp: stream service : rtmp: receive thread : handle publish message : rtmp: consume message : rtmp: consume video : meta update video : demux SPS/PPS : avc decode sequence header
thread [22026][fh669396]: do_cycle() [src/app/srs_app_rtmp_conn.cpp:213][errno=4]
thread [22026][fh669396]: service_cycle() [src/app/srs_app_rtmp_conn.cpp:410][errno=4]
thread [22026][fh669396]: do_publishing() [src/app/srs_app_rtmp_conn.cpp:900][errno=11]
thread [22026][fh669396]: consume() [src/app/srs_app_recv_thread.cpp:399][errno=11]
thread [22026][fh669396]: handle_publish_message() [src/app/srs_app_rtmp_conn.cpp:1003][errno=11]
thread [22026][fh669396]: process_publish_message() [src/app/srs_app_rtmp_conn.cpp:1031][errno=11]
thread [22026][fh669396]: on_video_imp() [src/app/srs_app_source.cpp:2356][errno=11]
thread [22026][fh669396]: video_avc_demux() [src/kernel/srs_kernel_codec.cpp:767][errno=11]
thread [22026][fh669396]: avc_demux_sps_pps() [src/kernel/srs_kernel_codec.cpp:794][errno=11](Interrupted system call)

对应的报错代码段为

srs_error_t SrsFormat::avc_demux_sps_pps(SrsBuffer* stream)
{
    // AVCDecoderConfigurationRecord
    // 5.2.4.1.1 Syntax, ISO_IEC_14496-15-AVC-format-2012.pdf, page 16
    int avc_extra_size = stream->size() - stream->pos();
    if (avc_extra_size > 0) {
        char *copy_stream_from = stream->data() + stream->pos();
        vcodec->avc_extra_data = std::vector<char>(copy_stream_from, copy_stream_from + avc_extra_size);
    }
    
    if (!stream->require(6)) {
        return srs_error_new(ERROR_HLS_DECODE_ERROR, "avc decode sequence header");
    }

看来是取不到AVCC。按照这个线索,去调试一下ffmpeg代码。写入avcc的代码在libavformat/flvenc.c的flv_write_codec_header

    if (par->codec_id == AV_CODEC_ID_AAC || par->codec_id == AV_CODEC_ID_H264
            || par->codec_id == AV_CODEC_ID_MPEG4) {
...
        if (par->codec_id == AV_CODEC_ID_AAC) {
...
        } else {
            avio_w8(pb, par->codec_tag | FLV_FRAME_KEY); // flags
            avio_w8(pb, 0); // AVC sequence header
            avio_wb24(pb, 0); // composition time
            ff_isom_write_avcc(pb, par->extradata, par->extradata_size);
        }

在这里打印一下par->extradata_size,发现长度为0。说明在解复用获取参数那一步还是有问题。进一步阅读ffmpeg代码查找问题原因。在 libavformat/utils.c 的 avformat_find_stream_info中,其执行逻辑大概为

通过打日志,发现不完全的ffmpeg编译配置,在解码并got_picture之后,avctx->extradata_size依然为0。但是完整的ffmpeg编译,avctx->extradata_size在got_picture不为0了。进一步的调试,发现在遇到sps、pps的时候,还没进入try_decode_frame ,extradata_size已经不为0了。再看一眼代码,发现

        if (!st->internal->avctx->extradata) {
            ret = extract_extradata(ic, st, pkt);
            if (ret < 0)
                goto unref_then_goto_end;
        }

        /* If still no information, we try to open the codec and to
         * decompress the frame. We try to avoid that in most cases as
         * it takes longer and uses more memory. For MPEG-4, we need to
         * decompress for QuickTime.
         *
         * If AV_CODEC_CAP_CHANNEL_CONF is set this will force decoding of at
         * least one frame of codec data, this makes sure the codec initializes
         * the channel configuration and does not only trust the values from
         * the container. */
        try_decode_frame(ic, st, pkt,
                         (options && i < orig_nb_streams) ? &options[i] : NULL);

前面还调用了一个extract_extradata,extract_extradata有个初始化步骤

    f = av_bsf_get_by_name("extract_extradata");
    if (!f)
        goto finish;

这里需要调用一个bitstream filter extract_extradata,显然我们没有把它编译起来,所以就没有生效。顺便粗略地看了一眼 libavcodec/extract_extradata_bsf.c,大致就是针对h264、h265码流,从码流中分解出一个个nal,然后把sps、pps拷贝出来。因此我们把ffmpeg的编译调整为

./configure --disable-everything --disable-x86asm --enable-protocol=rtmp --enable-demuxer=rtsp --enable-parser=h264 --enable-decoder=h264 --enable-bsf=extract_extradata --enable-muxer=flv

就可以正常工作了

上一篇 下一篇

猜你喜欢

热点阅读