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

Posted

技术标签:

【中文标题】使用 libavformat 通过 RTP 流式传输 H.264【英文标题】:streaming H.264 over RTP with libavformat 【发布时间】:2012-04-25 23:45:48 【问题描述】:

过去一周我一直在尝试通过 RTP 实现 H.264 流,使用 x264 作为编码器和 libavformat 来打包和发送流。问题是,据我所知,它工作不正常。

现在我只是编码随机数据 (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 用于将帧安全地传递给 Streamer 类,然后将帧发送出去。现在它没有线程化,因为我只是在测试试图让它工作。在对单个帧进行编码之前,我已经确保初始化了编码器。

但我不认为 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(&amp;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 的一点版权 blob。 但是,我用过的任何播放器都无法理解这些数据。 VLC 不再需要 SDP 描述,apparently isn't required。

然后我尝试通过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),或者在 depayloader 之前放置一个 capsfilter 元素和 在上面设置'caps'属性。另见 http://cgit.freedesktop.org/gstreamer/gst-plugins-good/tree/gst/rtp/README

由于我没有使用 GStreamer 进行流式传输,因此我不太确定 RTP 上限意味着什么。但是,这让我想知道我是否没有通过 RTP 发送足够的信息来描述流。我对视频很陌生,我觉得这里缺少一些关键的东西。有什么提示吗?

【问题讨论】:

【参考方案1】:

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

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

GStreamer 期望接收符合 RTP 协议的数据。您是否期望 libaformat 会生成 GStreamer 立即可读的 RTP 数据包?也许 GStreamers 期望一个额外的流描述,使其能够使用适当的解码器接受和解码流数据包?也许它需要额外的 RTSP 交换或 SDP 流描述符文件?

错误消息非常清楚地表明尚未协商 RTP 格式。 caps 是功能的简写。接收器需要知道发射器的能力才能正确设置接收器/解码机制。

我强烈建议至少尝试为您的 RTP 流创建一个 SDP 文件。 libavformatshould be able to do it for you.

【讨论】:

就是这样——我不知道,而且我很难找到我需要的信息。据我所知,libavformat 会为您将内容打包到 RTP 流中(并且不会发送无效数据包——我已经尝试过)。它不进行任何 RTSP 协商;最终这将指向 Feng 或其他一些外部应用程序来处理向客户端发送的 RTSP 流。但是,这并不能解释为什么没有任何东西可以使 libavformat 生成的 RTP 流产生正面或反面。 你需要以某种方式协商它。为什么不尝试为您的流创建 SDP 文件? 我试了一下,我可以让 VLC 显示绿屏——我不知道这是否正确,但这是一个开始。今天将着手解决这个问题,所以我们会看看这是否真的是问题所在。 嗯,你是说你正在编码随机数据。不要那样做 - 作为第一步,读取真实图像并一遍又一遍地对其进行编码。 这是另一个建议。为什么不从 ffserver.c 中已经提供的代码开始,然后添加您需要的任何功能?或者至少你可以参考一下。

以上是关于使用 libavformat 通过 RTP 流式传输 H.264的主要内容,如果未能解决你的问题,请参考以下文章

是否可以通过 RTMP 或 RTP 流式传输到 Twilio 视频 API

在 ffplay 中获得绿屏:使用 Live555 通过 RTP 流将桌面(DirectX 表面)流式传输为 H264 视频

Libavformat- 将图像对象传递给 libavformat 以生成视频

尝试通过RTP cpp使用ffmpeg库发送视频流时数据包丢失

用于摄像机的 HTTP 隧道(RTP)

FFMPEG 流式传输 RTP:未设置时基