使用libavformat通过RTP传输H.264

Jac*_*ord 10 c++ rtp h.264 libx264 libavformat

过去一周我一直在尝试通过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);
    }
}
Run Code Online (Sandbox Code Playgroud)

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);
}
Run Code Online (Sandbox Code Playgroud)

这是事情似乎出错的地方.av_write_header上面似乎绝对没有; 我用过wireshark验证了这一点.作为参考,我Streamer streamer(&enc, "10.89.6.3", 49990, 800, 600, 30, 40000);用来初始化Streamer实例,enc作为对Encoder先前用于处理x264 的对象的引用.

现在,当我想流出一个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);
Run Code Online (Sandbox Code Playgroud)

此时,我可以看到RTP数据出现在网络上,它看起来像我发送的帧,甚至包括来自x264的一点版权blob.但是,我使用的任何玩家都无法理解数据.VLC退出想要SDP描述,这显然不是必需的.

然后我尝试通过gst-launch:

gst-launch udpsrc port=49990 ! rtph264depay ! decodebin ! xvimagesink

这将等待UDP数据,但收到后,我得到:

错误:element/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发送足够的信息来描述流.我对视频很陌生,我觉得我在这里缺少一些关键的东西.任何提示?

Geo*_*sov 4

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

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

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

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

我强烈建议至少尝试为您的 RTP 流创建一个 SDP 文件。libavformat应该能够为你做到这一点