Pregunta

He estado trabajando en algún software de transmisión que toma alimentos en vivo de varios tipos de cámaras y arroyos sobre la red usando H.264. Para lograr esto, estoy usando el codificador X264 directamente (con el preajuste de "zerolatencia") y alimentar a los nales a medida que están disponibles para LibavFormat para empacar en RTP (en última instancia, RTSP). Idealmente, esto La solicitud debe ser lo más real posible. En la mayor parte, Esto ha estado funcionando bien.

Desafortunadamente, sin embargo, hay algún tipo de problema de sincronización: Cualquier reproducción de video en los clientes parece mostrar algunos marcos lisos, seguido de una breve pausa, luego más marcos; repetir. Adicionalmente, Parece que hay aproximadamente un retraso de 4 segundos. Esto sucede con Cada jugador de video que he intentado: Totem, VLC y Tubos básicos de GSTEMER.

Lo he hecho hasta un caso de prueba algo pequeño:

#include <stdio.h>
#include <stdint.h>
#include <unistd.h>
#include <x264.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>

#define WIDTH       640
#define HEIGHT      480
#define FPS         30
#define BITRATE     400000
#define RTP_ADDRESS "127.0.0.1"
#define RTP_PORT    49990

struct AVFormatContext* avctx;
struct x264_t* encoder;
struct SwsContext* imgctx;

uint8_t test = 0x80;


void create_sample_picture(x264_picture_t* picture)
{
    // create a frame to store in
    x264_picture_alloc(picture, X264_CSP_I420, WIDTH, HEIGHT);

    // fake image generation
    // disregard how wrong this is; just writing a quick test
    int strides = WIDTH / 8;
    uint8_t* data = malloc(WIDTH * HEIGHT * 3);
    memset(data, test, WIDTH * HEIGHT * 3);
    test = (test << 1) | (test >> (8 - 1));

    // scale the image
    sws_scale(imgctx, (const uint8_t* const*) &data, &strides, 0, HEIGHT,
              picture->img.plane, picture->img.i_stride);
}

int encode_frame(x264_picture_t* picture, x264_nal_t** nals)
{
    // encode a frame
    x264_picture_t pic_out;
    int num_nals;
    int frame_size = x264_encoder_encode(encoder, nals, &num_nals, picture, &pic_out);

    // ignore bad frames
    if (frame_size < 0)
    {
        return frame_size;
    }

    return num_nals;
}

void stream_frame(uint8_t* payload, int size)
{
    // initalize a packet
    AVPacket p;
    av_init_packet(&p);
    p.data = payload;
    p.size = size;
    p.stream_index = 0;
    p.flags = AV_PKT_FLAG_KEY;
    p.pts = AV_NOPTS_VALUE;
    p.dts = AV_NOPTS_VALUE;

    // send it out
    av_interleaved_write_frame(avctx, &p);
}

int main(int argc, char* argv[])
{
    // initalize ffmpeg
    av_register_all();

    // set up image scaler
    // (in-width, in-height, in-format, out-width, out-height, out-format, scaling-method, 0, 0, 0)
    imgctx = sws_getContext(WIDTH, HEIGHT, PIX_FMT_MONOWHITE,
                            WIDTH, HEIGHT, PIX_FMT_YUV420P,
                            SWS_FAST_BILINEAR, NULL, NULL, NULL);

    // set up encoder presets
    x264_param_t param;
    x264_param_default_preset(&param, "ultrafast", "zerolatency");

    param.i_threads = 3;
    param.i_width = WIDTH;
    param.i_height = HEIGHT;
    param.i_fps_num = FPS;
    param.i_fps_den = 1;
    param.i_keyint_max = FPS;
    param.b_intra_refresh = 0;
    param.rc.i_bitrate = BITRATE;
    param.b_repeat_headers = 1; // whether to repeat headers or write just once
    param.b_annexb = 1;         // place start codes (1) or sizes (0)

    // initalize
    x264_param_apply_profile(&param, "high");
    encoder = x264_encoder_open(&param);

    // at this point, x264_encoder_headers can be used, but it has had no effect

    // set up streaming context. a lot of error handling has been ommitted
    // for brevity, but this should be pretty standard.
    avctx = avformat_alloc_context();
    struct AVOutputFormat* fmt = av_guess_format("rtp", NULL, NULL);
    avctx->oformat = fmt;

    snprintf(avctx->filename, sizeof(avctx->filename), "rtp://%s:%d", RTP_ADDRESS, RTP_PORT);
    if (url_fopen(&avctx->pb, avctx->filename, URL_WRONLY) < 0)
    {
        perror("url_fopen failed");
        return 1;
    }
    struct AVStream* stream = av_new_stream(avctx, 1);

    // initalize codec
    AVCodecContext* c = stream->codec;
    c->codec_id = CODEC_ID_H264;
    c->codec_type = AVMEDIA_TYPE_VIDEO;
    c->flags = CODEC_FLAG_GLOBAL_HEADER;
    c->width = WIDTH;
    c->height = HEIGHT;
    c->time_base.den = FPS;
    c->time_base.num = 1;
    c->gop_size = FPS;
    c->bit_rate = BITRATE;
    avctx->flags = AVFMT_FLAG_RTP_HINT;

    // write the header
    av_write_header(avctx);

    // make some frames
    for (int frame = 0; frame < 10000; frame++)
    {
        // create a sample moving frame
        x264_picture_t* pic = (x264_picture_t*) malloc(sizeof(x264_picture_t));
        create_sample_picture(pic);

        // encode the frame
        x264_nal_t* nals;
        int num_nals = encode_frame(pic, &nals);

        if (num_nals < 0)
            printf("invalid frame size: %d\n", num_nals);

        // send out NALs
        for (int i = 0; i < num_nals; i++)
        {
            stream_frame(nals[i].p_payload, nals[i].i_payload);
        }

        // free up resources
        x264_picture_clean(pic);
        free(pic);

        // stream at approx 30 fps
        printf("frame %d\n", frame);
        usleep(33333);
    }

    return 0;
}

Esta prueba muestra líneas negras sobre un fondo blanco que debe moverse suavemente hacia la izquierda. Se ha escrito para FFMPEG 0.6.5 Pero el problema se puede reproducir en 0.8 y 0.10 (de lo que he probado hasta ahora). He tomado algunos atajos en el manejo de errores para hacer este ejemplo tan corto como posible al tiempo que sigue mostrando el problema, así que por favor discule algunos de los Código desagradable. También debería tener en cuenta que, mientras que un SDP no se usa aquí, yo Han intentado usar eso con resultados similares. La prueba puede ser compilado con:

gcc -g -std=gnu99 streamtest.c -lswscale -lavformat -lx264 -lm -lpthread -o streamtest

Se puede jugar con Gtreamer directamente:

gst-launch udpsrc port=49990 ! application/x-rtp,payload=96,clock-rate=90000 ! rtph264depay ! decodebin ! xvimagesink

Debes notar inmediatamente el tartamudeo. Una "solución" común que he visto en todo el internet es agregar sincronización= falso a la tubería:

gst-launch udpsrc port=49990 ! application/x-rtp,payload=96,clock-rate=90000 ! rtph264depay ! decodebin ! xvimagesink sync=false

Esto hace que la reproducción sea suave (y casi en tiempo libre), pero es un No solución y solo funciona con GSTERMER. Me gustaria arreglar el Problema en la fuente. He podido transmitir con casi idéntico Parámetros que utilizan FFMPEG sin procesar y no han tenido ningún problema:

ffmpeg -re -i sample.mp4 -vcodec libx264 -vpre ultrafast -vpre baseline -b 400000 -an -f rtp rtp://127.0.0.1:49990 -an

tan claramente que estoy haciendo algo mal. Pero, ¿qué es?

¿Fue útil?

Solución

1) No estableció PTS para cuadros que envía a libx264 (probablemente deberías ver advertencias "no estrictamente monotonas")) 2) No estableció PTS / DTS para los paquetes que envía a RTP MUXER de LibavFormat (no estoy 100% seguro que debe configurarse, pero supongo que sería mejor. Desde el código fuente, se ve como RTP use PTS). 3) Imho USLEEP (33333) es malo.Causa que el codificador se bloquee esta vez también (aumento de la latencia) mientras podría codificar el siguiente marco durante este tiempo, incluso si aún no necesita enviarlo por RTP.

P.s.Por cierto, no estableció param.rc.I_RC_Method a X264_RC_ABR, por lo que Libx264 usará CRF 23 en su lugar e ignorará su "param.rc.i_Bitrate= BitRate".También puede ser una buena idea usar VBV al codificar para el envío de la red.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top