¿Cómo codifica una serie de imágenes en H264 usando la API x264 C?

¿Cómo se usa la API x264 C para codificar imágenes RBG en marcos H264? Ya creé una secuencia de imágenes RBG, ¿cómo puedo ahora transformar esa secuencia en una secuencia de cuadros H264? En particular, ¿cómo codifico esta secuencia de imágenes RGB en una secuencia de cuadro H264 que consta de un único fotogtwig clave H264 inicial seguido de cuadros H264 dependientes?

En primer lugar, compruebe el archivo x264.h, que contiene más o menos la referencia de cada función y estructura. El archivo x264.c que puede encontrar en la descarga contiene una implementación de muestra. La mayoría de la gente dice basarse en eso, pero me parece bastante complejo para principiantes, pero es bueno como un ejemplo para recurrir.

Primero configura algunos parámetros, del tipo x264_param_t, un buen sitio que describe los parámetros es http://mewiki.project357.com/wiki/X264_Settings . También eche un vistazo a la función x264_param_default_preset que le permite orientar algunas funcionalidades sin necesidad de comprender todos los parámetros (a veces bastante complejos). También use x264_param_apply_profile después (probablemente querrá el perfil “base”)

Esta es una configuración de 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 datos de YUV420P (supongo que algunos otros también, pero ese es el común). Puede usar libswscale (desde ffmpeg) para convertir imágenes al formato correcto. Inicializar esto es así (supongo que los datos RGB con 24bpp).

 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); 

la encoding es tan simple como esto, para cada cuadro:

 //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 ayude;), me pasé mucho tiempo para empezar. X264 es una pieza increíblemente fuerte pero a veces compleja de software.

editar: cuando usa otros parámetros, habrá marcos diferidos, este no es el caso con mis parámetros (principalmente debido a la opción de nolatencia). Si este es el caso, frame_size algunas veces será cero y tendrá que llamar a x264_encoder_encode siempre que la función x264_encoder_delayed_frames no devuelva 0. Pero para esta funcionalidad, debería echar un vistazo más profundo a x264.c y x264.h.

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

Ejemplo ejecutable de FFmpeg 2.8.6

Usar FFpmeg como envoltorio para x264 es una buena idea, ya que expone una API uniforme para múltiples codificadores. Entonces, si alguna vez necesita cambiar los formatos, puede cambiar solo un parámetro en lugar de aprender una nueva API.

El ejemplo sintetiza y codifica algunos marcos coloridos generados por generate_rgb .

El control del tipo de ttwig ( I, P, B ) para tener el menor número posible de fotogtwigs clave (idealmente el primero) se trata aquí: https://stackoverflow.com/a/36412909/895245 Como mencioné allí, no lo hago lo recomiendo para la mayoría de las aplicaciones.

Las líneas clave que controlan el tipo de marco 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; } 

Luego podemos verificar el tipo de marco con:

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

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

Vista previa de la salida generada .

 #include  #include  #include  #include  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; } 

Comstack y ejecuta con:

 gcc -std=c99 -Wextra ac -lavcodec -lswscale -lavutil ./a.out ffplay tmp.mpg ffplay tmp.h264 

Probado en Ubuntu 16.04. GitHub aguas arriba .