Como se codifica uma série de imagens no H264 usando a API x264 C?

Como usar a API x264 C para codificar imagens RBG em frameworks H264? Eu já criei uma sequência de imagens RBG, como posso agora transformar essa sequência em uma seqüência de frameworks H264? Em particular, como codifico essa seqüência de imagens RGB em uma sequência de frameworks H264 que consiste em um único quadro-chave H264 inicial seguido por frameworks H264 dependentes?

Primeiro de tudo: verifique o arquivo x264.h, ele contém mais ou menos a referência para cada function e estrutura. O arquivo x264.c que você pode encontrar no download contém uma implementação de amostra. A maioria das pessoas diz para se basear nisso, mas acho que é bastante complexo para iniciantes, mas é um bom exemplo para recorrer.

Primeiro você configura alguns parâmetros, do tipo x264_param_t, um bom site descrevendo parâmetros é http://mewiki.project357.com/wiki/X264_Settings . Também dê uma olhada na function x264_param_default_preset que permite que você direcione algumas funcionalidades sem precisar entender todos os parâmetros (às vezes bastante complexos). Use também x264_param_apply_profile depois (provavelmente você desejará o perfil “baseline”)

Este é um exemplo de configuração do meu 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"); 

Depois disso, você pode inicializar o codificador da seguinte forma

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

O X264 espera dados do YUV420P (eu acho que outros também, mas esse é o mais comum). Você pode usar o libswscale (do ffmpeg) para converter imagens para o formato correto. Inicializando isso é assim (eu assumo dados RGB com 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); 

codificação é tão simples como isso, então, para cada quadro faça:

 //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 isso faça você ir;), eu passei muito tempo nisso para começar. O X264 é um software insanamente forte, mas às vezes complexo.

edit: Quando você usa outros parâmetros, haverá frames atrasados, isso não é o caso com os meus parâmetros (principalmente devido à opção nolatency). Se este for o caso, frame_size será algumas vezes zero e você terá que chamar x264_encoder_encode desde que a function x264_encoder_delayed_frames não retorne 0. Mas para esta funcionalidade você deve dar uma olhada mais profunda em x264.c e x264.h.

Eu carreguei um exemplo que gera frameworks yuv crus e, em seguida, os codifica usando x264. O código completo pode ser encontrado aqui: https://gist.github.com/roxlu/6453908

FFmpeg 2.8.6 exemplo executável

Usar o FFpmeg como um wrapper para o x264 é uma boa ideia, pois expõe uma API uniforme para vários codificadores. Portanto, se você precisar alterar os formatos, poderá alterar apenas um parâmetro em vez de aprender uma nova API.

O exemplo sintetiza e codifica alguns frameworks coloridos gerados pelo generate_rgb .

O controle do tipo de quadro ( I, P, B ) para ter o menor número possível de frameworks-chave (idealmente apenas o primeiro) é discutido aqui: https://stackoverflow.com/a/36412909/895245 Como mencionado aqui, eu não recomendá-lo para a maioria das aplicações.

As linhas-chave que controlam o tipo de quadro são:

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

e:

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

Podemos então verificar o tipo de quadro com:

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

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

Visualização da saída gerada .

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

Compile e execute com:

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

Testado no Ubuntu 16.04. GitHub upstream .