使用libavformat通过RTP流传输H.264视频

10
我过去一周一直尝试使用x264作为编码器和libavformat打包和发送流来实现H.264 RTP流。问题是,据我所知它似乎没有正常工作。
目前我只是对随机数据(x264_picture_alloc)进行编码并从libx264中提取NAL帧。这相当简单:
x264_picture_t pic_out;
x264_nal_t* nals;
int num_nals;
int frame_size = x264_encoder_encode(this->encoder, &nals, &num_nals, this->pic_in, &pic_out);

if (frame_size <= 0)
{
    return frame_size;
}

// push NALs into the queue
for (int i = 0; i < num_nals; i++)
{
    // create a NAL storage unit
    NAL nal;
    nal.size = nals[i].i_payload;
    nal.payload = new uint8_t[nal.size];
    memcpy(nal.payload, nals[i].p_payload, nal.size);

    // push the storage into the NAL queue
    {
        // lock and push the NAL to the queue
        boost::mutex::scoped_lock lock(this->nal_lock);
        this->nal_queue.push(nal);
    }
}

nal_queue被用于安全地将帧传递到一个流媒体类中,然后该类将发送这些帧。目前它还没有使用线程,因为我只是在测试中尝试让其工作。在对单个帧进行编码之前,我确保已经初始化了编码器。

但我不认为x264是问题所在,因为我可以看到它返回的NAL中的帧数据。使用libavformat来流式传输数据,首先需要在一个Streamer类中进行初始化:

Streamer::Streamer(Encoder* encoder, string rtp_address, int rtp_port, int width, int height, int fps, int bitrate)
{
    this->encoder = encoder;

    // initalize the AV context
    this->ctx = avformat_alloc_context();
    if (!this->ctx)
    {
        throw runtime_error("Couldn't initalize AVFormat output context");
    }

    // get the output format
    this->fmt = av_guess_format("rtp", NULL, NULL);
    if (!this->fmt)
    {
        throw runtime_error("Unsuitable output format");
    }
    this->ctx->oformat = this->fmt;

    // try to open the RTP stream
    snprintf(this->ctx->filename, sizeof(this->ctx->filename), "rtp://%s:%d", rtp_address.c_str(), rtp_port);
    if (url_fopen(&(this->ctx->pb), this->ctx->filename, URL_WRONLY) < 0)
    {
        throw runtime_error("Couldn't open RTP output stream");
    }

    // add an H.264 stream
    this->stream = av_new_stream(this->ctx, 1);
    if (!this->stream)
    {
        throw runtime_error("Couldn't allocate H.264 stream");
    }

    // initalize codec
    AVCodecContext* c = this->stream->codec;
    c->codec_id = CODEC_ID_H264;
    c->codec_type = AVMEDIA_TYPE_VIDEO;
    c->bit_rate = bitrate;
    c->width = width;
    c->height = height;
    c->time_base.den = fps;
    c->time_base.num = 1;

    // write the header
    av_write_header(this->ctx);
}

这是出错的地方。上面的av_write_header似乎完全没有作用;我已经使用wireshark验证过了。参考资料,我使用Streamer streamer(&enc, "10.89.6.3", 49990, 800, 600, 30, 40000);初始化Streamer实例,其中enc是对之前处理x264的Encoder对象的引用。
现在,当我想要流出NAL时,我使用以下代码:
// grab a NAL
NAL nal = this->encoder->nal_pop();
cout << "NAL popped with size " << nal.size << endl;

// initalize a packet
AVPacket p;
av_init_packet(&p);
p.data = nal.payload;
p.size = nal.size;
p.stream_index = this->stream->index;

// send it out
av_write_frame(this->ctx, &p);

在这个阶段,我可以看到RTP数据出现在网络上,并且它看起来是我发送的帧,甚至包括来自x264的版权小块。但是,我使用的任何播放器都不能理解这些数据。VLC退出并要求SDP描述,显然不是必需的
然后我尝试通过gst-launch播放它: gst-launch udpsrc port=49990 ! rtph264depay ! decodebin ! xvimagesink 这将等待UDP数据,但是当接收到时,我会得到:
错误:元素/GstPipeline:pipeline0/GstRtpH264Depay:rtph264depay0:没有协商RTP格式。附加调试信息:gstbasertpdepayload.c(372):gst_base_rtp_depayload_chain():/GstPipeline:pipeline0/GstRtpH264Depay:rtph264depay0:输入缓冲区需要在其上设置RTP caps。这通常是通过设置上游源元素(通常为udpsrc或appsrc)的“caps”属性,或者在解码器之前放置一个capsfilter元素并在其上设置“caps”属性来实现的。另请参见http://cgit.freedesktop.org/gstreamer/gst-plugins-good/tree/gst/rtp/README

由于我没有使用GStreamer本身进行流媒体传输,我不太确定它所说的RTP caps是什么意思。但是,它让我想知道是否没有发送足够的信息通过RTP来描述流。我对视频还很陌生,感觉自己在这里错过了一些重要的东西。有什么提示吗?

1个回答

4

h264 是一种编码标准,它指定了视频数据的压缩和存储格式,可以在以后解压成视频流。

RTP 是一种传输协议。它规定了可以携带由任意编码器编码的音视频数据的数据包的格式和顺序。

GStreamer 期望接收符合 RTP 协议的数据。你期望 libaformat 会生成立即可被 GStreamer 读取的 RTP 数据包吗?也许 GStreamer 需要一个附加的流描述来允许其使用适当的解码器接受和解码流数据包?或者它需要进行额外的 RTSP 交换或 SDP 流描述文件?

错误消息清楚地表明没有协商 RTP 格式。"caps" 是能力的简称。接收方需要知道发送方的能力,才能正确设置接收/解码机器。

我强烈建议至少尝试为您的 RTP 流创建一个 SDP 文件。libavformat 应该能够为您完成


那就是问题所在——我不知道,而且我很难找到我需要的信息。据我所知,libavformat会将东西打包成RTP流(而且不会发送无效的数据包——我已经尝试过)。它不会进行任何RTSP协商;最终这将指向Feng或其他外部应用程序来处理RTSP流传输给客户端。然而,这并不能解释为什么没有什么东西能够理解libavformat生成的RTP流。 - Jacob Peddicord
你需要想办法进行协商。为什么不尝试为你的流创建一个SDP文件呢? - George Skoptsov
我尝试了一下,我可以让VLC显示绿屏 - 是否正确我不知道,但这是一个开始。今天会继续努力,看看这是否真的是问题所在。 - Jacob Peddicord
1
好的,你说你正在编码随机数据。不要这样做——首先,读入一张真实的图像,然后反复对其进行编码。 - George Skoptsov
这里有另一个建议。为什么不从ffserver.c中提供的代码开始,然后添加您需要的任何功能呢?或者至少您可以将其用作参考。 - George Skoptsov
结果证明,这确实是问题所在;VLC和GStreamer都没有提供有关流的信息。我能够在VLC中播放实际的图像数据。对于gst-launch,添加类型和速率也起作用:gst-launch udpsrc port=49990 ! application/x-rtp,clock-rate=90000,payload=96 ! rtph264depay ! decodebin ! xvimagesink - Jacob Peddicord

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接