На прошлой неделе я пытался реализовать потоковую передачу H.264 через RTP, используя x264 в качестве кодировщика и libavformat для упаковки и отправки потока. Проблема в том, насколько я могу судить, он работает неправильно.
Прямо сейчас я просто кодирую случайные данные (x264_picture_alloc) и извлекаю кадры NAL из libx264. Это довольно просто:
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(&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);
В этот момент я вижу данные RTP, появляющиеся по сети, и это похоже на кадры, которые я отправлял, даже включая немного блок копирайта от x264. Нони один проигрыватель, с которым я работал, не смог разобраться в данных. VLC отказывается от описания SDP, которое очевидно не требуется.
Затем я попытался воспроизвести его через gst-launch
:
gst-launch udpsrc port=49990 ! rtph264depay ! декобин! xvimagesink
Это будет ждать данных UDP, но когда они будут получены, я получаю:
ОШИБКА: элемент /GstPipeline:pipeline0/GstRtpH264Depay:rtph264depay0: No RTP формат был обговорен. Дополнительная отладочная информация: gstbasertpdepayload.c(372): gst_base_rtp_depayload_chain (): /GstPipeline:pipeline0/GstRtpH264Depay:rtph264depay0: Входные буферы необходимо установить на них ограничения RTP. Обычно это достигается установкой свойство 'caps' вышестоящего исходного элемента (часто udpsrc или appsrc) или поместив элемент capsfilter перед depayloader и установив для этого свойство 'caps'. Также см http://cgit.freedesktop.org/gstreamer/gst-plugins-good/tree/gst/rtp/README
Поскольку я не использую GStreamer для потоковой передачи, я не совсем уверен, что это значит с шапками RTP. Но это заставляет меня задаться вопросом, не отправляю ли я достаточно информации по RTP для описания потока. Я новичок в видео, и мне кажется, что я упускаю здесь что-то ключевое.Любые подсказки?