¿Cómo se puede codificar una serie de imágenes en H264 utilizando el API de C x264?

StackOverflow https://stackoverflow.com/questions/2940671

  •  05-10-2019
  •  | 
  •  

Pregunta

¿Cómo se utiliza la API de C x 264 para codificar imágenes en RBG H264 marcos? Ya he creado una secuencia de imágenes RBG, ¿cómo puedo ahora transformar esa secuencia en una secuencia de fotogramas H264? En particular, ¿cómo codificar esta secuencia de imágenes RGB en una secuencia de marco de H264 que consiste en un único fotograma clave inicial H264 seguido de marcos H264 dependientes?

¿Fue útil?

Solución

En primer lugar: comprobar el archivo x264.h, que contiene más o menos la referencia para cada función y estructura. El archivo x264.c se pueden encontrar en la descarga contiene una implementación de ejemplo. La mayoría dice que para hacer base en eso, pero me resulta bastante complejo para los principiantes, es bueno como un ejemplo para caer de nuevo sin embargo.

En primer lugar, configurar algunos parámetros, del tipo x264_param_t, un sitio de buena descripción de parámetros es http://mewiki.project357.com/wiki/X264_Settings . También echa un vistazo a la función x264_param_default_preset que le permite apuntar algunas funciones sin necesidad de entender todos los parámetros (a veces bastante complejos). También uso x264_param_apply_profile después (probablemente querrá que la "línea de base" perfil)

Este es algún tipo de configuración ejemplo de mi código:

x264_param_t param;
x264_param_default_preset(&param, "veryfast", "zerolatency");
param.i_threads = 1;
param.i_width = width;
param.i_height = height;
param.i_fps_num = fps;
param.i_fps_den = 1;
// Intra refres:
param.i_keyint_max = fps;
param.b_intra_refresh = 1;
//Rate control:
param.rc.i_rc_method = X264_RC_CRF;
param.rc.f_rf_constant = 25;
param.rc.f_rf_constant_max = 35;
//For streaming:
param.b_repeat_headers = 1;
param.b_annexb = 1;
x264_param_apply_profile(&param, "baseline");

Después de esto puede inicializar el codificador de la siguiente manera

x264_t* encoder = x264_encoder_open(&param);
x264_picture_t pic_in, pic_out;
x264_picture_alloc(&pic_in, X264_CSP_I420, w, h)

X264 espera YUV420P de datos (supongo que algunos otros también, pero eso es el común). Puede utilizar libswscale (de ffmpeg) para convertir las imágenes en el formato correcto. Inicialización de esto es como este (supongo datos RGB con 24 bpp).

struct SwsContext* convertCtx = sws_getContext(in_w, in_h, PIX_FMT_RGB24, out_w, out_h, PIX_FMT_YUV420P, SWS_FAST_BILINEAR, NULL, NULL, NULL);

codificación es tan simple como esto, entonces, para hacer cada trama:

//data is a pointer to you RGB structure
int srcstride = w*3; //RGB stride is just 3*width
sws_scale(convertCtx, &data, &srcstride, 0, h, pic_in.img.plane, pic_in.img.stride);
x264_nal_t* nals;
int i_nals;
int frame_size = x264_encoder_encode(encoder, &nals, &i_nals, &pic_in, &pic_out);
if (frame_size >= 0)
{
    // OK
}

espero que esto te va;), pasé mucho tiempo en él mismo para empezar. X264 es una pieza increíblemente fuerte, pero a veces compleja del software.

Edit: Al utilizar otros parámetros no se retrasará marcos, este no es el caso con mis parámetros (sobre todo debido a la opción nolatency). Si este es el caso, frame_size veces habrá cero y que tendrá que llamar x264_encoder_encode siempre y cuando el x264_encoder_delayed_frames función no devuelve 0. Sin embargo, para que esta funcionalidad debe tener una mirada más profunda en x264.c y x264.h.

Otros consejos

He subido un ejemplo que genera marcos yuv cruda y luego los codifica utilizando x264. código completo se puede encontrar aquí: https://gist.github.com/roxlu/6453908

FFmpeg 2.8.6 C ejemplo runnable

El uso de FFpmeg como un contenedor para x264 es una buena idea, ya que expone una API uniforme para varios codificadores. Así que si alguna vez tiene que cambiar formatos, puede cambiar un solo parámetro en lugar de aprender un nuevo API.

Los sintetiza ejemplo y codifica algunos marcos de colores generados por generate_rgb.

Control de tipo de trama ( I, P, B ) para tener tan pocas número- marcos como sea posible (lo ideal es sólo la primera) se discute aquí: https://stackoverflow.com/a/36412909/895245 Como se menciona allí, yo no lo recomiendo para la mayoría de aplicaciones.

Las principales líneas que hacen el control de tipo de trama aquí son:

/* Minimal distance of I-frames. This is the maximum value allowed,
or else we get a warning at runtime. */
c->keyint_min = 600;

y

if (frame->pts == 1) {
    frame->key_frame = 1;
    frame->pict_type = AV_PICTURE_TYPE_I;
} else {
    frame->key_frame = 0;
    frame->pict_type = AV_PICTURE_TYPE_P;
}

A continuación, podemos verificar el tipo de trama con:

ffprobe -select_streams v \
    -show_frames \
    -show_entries frame=pict_type \
    -of csv \
    tmp.h264

como se mencionó al: https: / /superuser.com/questions/885452/extracting-the-index-of-key-frames-from-a-video-using-ffmpeg

preliminar del generada de salida.

main.c

#include <libavcodec/avcodec.h>
#include <libavutil/imgutils.h>
#include <libavutil/opt.h>
#include <libswscale/swscale.h>

static AVCodecContext *c = NULL;
static AVFrame *frame;
static AVPacket pkt;
static FILE *file;
struct SwsContext *sws_context = NULL;

static void ffmpeg_encoder_set_frame_yuv_from_rgb(uint8_t *rgb) {
    const int in_linesize[1] = { 3 * c->width };
    sws_context = sws_getCachedContext(sws_context,
            c->width, c->height, AV_PIX_FMT_RGB24,
            c->width, c->height, AV_PIX_FMT_YUV420P,
            0, 0, 0, 0);
    sws_scale(sws_context, (const uint8_t * const *)&rgb, in_linesize, 0,
            c->height, frame->data, frame->linesize);
}

uint8_t* generate_rgb(int width, int height, int pts, uint8_t *rgb) {
    int x, y, cur;
    rgb = realloc(rgb, 3 * sizeof(uint8_t) * height * width);
    for (y = 0; y < height; y++) {
        for (x = 0; x < width; x++) {
            cur = 3 * (y * width + x);
            rgb[cur + 0] = 0;
            rgb[cur + 1] = 0;
            rgb[cur + 2] = 0;
            if ((frame->pts / 25) % 2 == 0) {
                if (y < height / 2) {
                    if (x < width / 2) {
                        /* Black. */
                    } else {
                        rgb[cur + 0] = 255;
                    }
                } else {
                    if (x < width / 2) {
                        rgb[cur + 1] = 255;
                    } else {
                        rgb[cur + 2] = 255;
                    }
                }
            } else {
                if (y < height / 2) {
                    rgb[cur + 0] = 255;
                    if (x < width / 2) {
                        rgb[cur + 1] = 255;
                    } else {
                        rgb[cur + 2] = 255;
                    }
                } else {
                    if (x < width / 2) {
                        rgb[cur + 1] = 255;
                        rgb[cur + 2] = 255;
                    } else {
                        rgb[cur + 0] = 255;
                        rgb[cur + 1] = 255;
                        rgb[cur + 2] = 255;
                    }
                }
            }
        }
    }
    return rgb;
}

/* Allocate resources and write header data to the output file. */
void ffmpeg_encoder_start(const char *filename, int codec_id, int fps, int width, int height) {
    AVCodec *codec;
    int ret;

    codec = avcodec_find_encoder(codec_id);
    if (!codec) {
        fprintf(stderr, "Codec not found\n");
        exit(1);
    }
    c = avcodec_alloc_context3(codec);
    if (!c) {
        fprintf(stderr, "Could not allocate video codec context\n");
        exit(1);
    }
    c->bit_rate = 400000;
    c->width = width;
    c->height = height;
    c->time_base.num = 1;
    c->time_base.den = fps;
    c->keyint_min = 600;
    c->pix_fmt = AV_PIX_FMT_YUV420P;
    if (codec_id == AV_CODEC_ID_H264)
        av_opt_set(c->priv_data, "preset", "slow", 0);
    if (avcodec_open2(c, codec, NULL) < 0) {
        fprintf(stderr, "Could not open codec\n");
        exit(1);
    }
    file = fopen(filename, "wb");
    if (!file) {
        fprintf(stderr, "Could not open %s\n", filename);
        exit(1);
    }
    frame = av_frame_alloc();
    if (!frame) {
        fprintf(stderr, "Could not allocate video frame\n");
        exit(1);
    }
    frame->format = c->pix_fmt;
    frame->width  = c->width;
    frame->height = c->height;
    ret = av_image_alloc(frame->data, frame->linesize, c->width, c->height, c->pix_fmt, 32);
    if (ret < 0) {
        fprintf(stderr, "Could not allocate raw picture buffer\n");
        exit(1);
    }
}

/*
Write trailing data to the output file
and free resources allocated by ffmpeg_encoder_start.
*/
void ffmpeg_encoder_finish(void) {
    uint8_t endcode[] = { 0, 0, 1, 0xb7 };
    int got_output, ret;
    do {
        fflush(stdout);
        ret = avcodec_encode_video2(c, &pkt, NULL, &got_output);
        if (ret < 0) {
            fprintf(stderr, "Error encoding frame\n");
            exit(1);
        }
        if (got_output) {
            fwrite(pkt.data, 1, pkt.size, file);
            av_packet_unref(&pkt);
        }
    } while (got_output);
    fwrite(endcode, 1, sizeof(endcode), file);
    fclose(file);
    avcodec_close(c);
    av_free(c);
    av_freep(&frame->data[0]);
    av_frame_free(&frame);
}

/*
Encode one frame from an RGB24 input and save it to the output file.
Must be called after ffmpeg_encoder_start, and ffmpeg_encoder_finish
must be called after the last call to this function.
*/
void ffmpeg_encoder_encode_frame(uint8_t *rgb) {
    int ret, got_output;
    ffmpeg_encoder_set_frame_yuv_from_rgb(rgb);
    av_init_packet(&pkt);
    pkt.data = NULL;
    pkt.size = 0;
    if (frame->pts == 1) {
        frame->key_frame = 1;
        frame->pict_type = AV_PICTURE_TYPE_I;
    } else {
        frame->key_frame = 0;
        frame->pict_type = AV_PICTURE_TYPE_P;
    }
    ret = avcodec_encode_video2(c, &pkt, frame, &got_output);
    if (ret < 0) {
        fprintf(stderr, "Error encoding frame\n");
        exit(1);
    }
    if (got_output) {
        fwrite(pkt.data, 1, pkt.size, file);
        av_packet_unref(&pkt);
    }
}

/* Represents the main loop of an application which generates one frame per loop. */
static void encode_example(const char *filename, int codec_id) {
    int pts;
    int width = 320;
    int height = 240;
    uint8_t *rgb = NULL;
    ffmpeg_encoder_start(filename, codec_id, 25, width, height);
    for (pts = 0; pts < 100; pts++) {
        frame->pts = pts;
        rgb = generate_rgb(width, height, pts, rgb);
        ffmpeg_encoder_encode_frame(rgb);
    }
    ffmpeg_encoder_finish();
}

int main(void) {
    avcodec_register_all();
    encode_example("tmp.h264", AV_CODEC_ID_H264);
    encode_example("tmp.mpg", AV_CODEC_ID_MPEG1VIDEO);
    return 0;
}

compilar y ejecutar con:

gcc -o main.out -std=c99 -Wextra main.c -lavcodec -lswscale -lavutil
./main.out
ffplay tmp.mpg
ffplay tmp.h264

Probado en Ubuntu 16.04. GitHub aguas arriba .

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