Потоковое H.264 через RTP с libavformat

Я пытался на прошлой неделе внедрить потоки 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 выше, кажется, ничего не делает; Я использовал wirehark, чтобы проверить это. Для справки я использую 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 ! decodebin ! xvimagesink

Это будет сидеть в ожидании UDP-данных, но когда он будет получен, я получаю:

ОШИБКА: элемент /GstPipeline: pipe0/GstRtpH264Depay: rtph264depay0: нет RTP формат был согласован. Дополнительная информация об отладке: gstbasertpdepayload.c(372): gst_base_rtp_depayload_chain(): /GstPipeline: pipe0/GstRtpH264Depay: rtph264depay0: входные буферы необходимо установить на них колпачки RTP. Обычно это достигается установкой свойство "caps" исходного элемента источника (часто udpsrc или appsrc), или путем добавления элемента capfilter перед дефайлоукладчиком и установив для этого свойство "caps". Также см http://cgit.freedesktop.org/gstreamer/gst-plugins-good/tree/gst/rtp/README

Поскольку я не использую GStreamer для потока, я не совсем уверен, что это означает с помощью RTP-кепок. Но, это заставляет меня задаться вопросом, не отправляю ли я достаточную информацию по RTP для описания потока. Я новичок в видео, и мне кажется, что здесь есть какая-то ключевая вещь. Любые подсказки?

Ответ 1

h264 - стандарт кодирования. Он определяет, как видеоданные сжимаются и сохраняются в формате, который может быть распакован в видеопоток в более поздней точке.

RTP - это протокол передачи. Он определяет формат и порядок пакетов, которые могут переносить аудио-видео данные, которые были закодированы с помощью произвольного кодировщика.

GStreamer ожидает получения данных, которые соответствуют проктологу RTP. Ожидаете ли вы, что libaformat будет производить RTP-пакеты, которые могут быть легко прочитаны GStreamer? Возможно, GStreamers ожидает дополнительное описание потока, которое позволит ему принимать и декодировать потоковые пакеты с использованием надлежащего декодера? Возможно, для этого требуется дополнительный обмен RTSP или файл дескриптора потока SDP?

Сообщение об ошибке довольно четко говорит о том, что формат RTP не был согласован. caps - это короткие возможности. Приемник должен знать возможности передатчика, чтобы правильно настроить приемник/декодер.

Я настоятельно рекомендую попробовать хотя бы создать файл SDP для вашего потока RTP. libavformat должен иметь возможность сделать это для вас.