2010-05-30 13 views
56

RBG görüntülerini H264 karelerine kodlamak için x264 C API nasıl kullanılır? Zaten bir dizi RBG görüntüsü oluşturdum, şimdi bu sırayı H264 çerçevelerinin bir dizisine nasıl dönüştürebilirim? Özellikle, bu RGB görüntü dizisini, tek bir başlangıç ​​H264 anahtar karesinden ve ardından H264 çerçevelerinden oluşan bir H264 karesi dizisine nasıl kodlarım?Biri x264 C API kullanarak bir dizi görüntüyü H264'e nasıl kodlar?

cevap

88

Her şeyden önce: x264.h dosyasını kontrol edin, her işlev ve yapı için daha fazla veya daha az başvuru içerir. İndirmede bulabileceğiniz x264.c dosyası örnek bir uygulama içerir. Çoğu insan bunu kendinizin üzerine koymayı söylüyor, ancak yeni başlayanlar için oldukça karmaşık buluyorum, ancak yine de geriye düşmek için bir örnek.

Önce, x264_param_t türünde, parametreleri tanımlayan iyi bir site olan http://mewiki.project357.com/wiki/X264_Settings türünde bazı parametreler ayarlayabilirsiniz. Ayrıca, bazı (bazen oldukça karmaşık) tüm parametreleri anlamaya gerek kalmadan bazı işlevleri hedeflemenize olanak veren x264_param_default_preset işlevine de bakın.

x264_t* encoder = x264_encoder_open(&param); 
x264_picture_t pic_in, pic_out; 
x264_picture_alloc(&pic_in, X264_CSP_I420, w, h) 
şöyle Kodçözücüyü başlatabilir Bundan sonra

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

: Ayrıca

Bu benim kodundan bazı örnek kurulduğundan (muhtemelen "taban" profili isteyeceksiniz) sonradan x264_param_apply_profile kullanmak

X264 YUV420P verilerini bekler (bazılarını da sanırım, ama bu ortak olan). Görüntüleri doğru formata dönüştürmek için libswscale'i (ffmpeg'den) kullanabilirsiniz. Bunun başlatılması şu şekildedir (RGB verisini 24bpp ile kabul ediyorum).

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

kodlama her kare için yapmak, o zaman bu kadar basittir:

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

Bu olacak almak umut;) kendimi Başlamak için üzerine uzun bir süre geçirdi. X264, delice güçlü ama bazen karmaşık bir yazılım parçasıdır.

düzenleme: Diğer parametreleri kullandığınızda gecikmiş çerçeveler olacak, bu benim parametrelerimde (çoğunlukla nolatency seçeneğinden dolayı) durum böyle değil. Bu durumda, frame_size bazen sıfır olacaktır ve x264_encoder_delayed_frames işlevi 0 döndürmediği sürece x264_encoder_encode'u çağırmanız gerekir. Ancak bu işlevsellik için x264.c ve x264.h içine daha derin bir göz atmalısınız.

+7

Bu çok yararlıdır (+1). Python topluluğu gerçekten bu C stili kodun bazılarını özetleyen bir sarıcıya ihtiyaç duyar. –

+1

Bunu normal bir medya istemcisine aktarmanın kolay bir yolu var mı, bir XBMC mi, yoksa bir AVI akışı olarak mı sarılıyor? – dascandy

+0

Bir DirectShow kaynak filtresi yazabilirsiniz. AVI, H.264 için en iyi konteynır tercihi değildir, bkz. Http://en.wikipedia.org/wiki/Comparison_of_container_formats – fishfood

5

Ham yuva çerçeveleri oluşturan bir örnek yükledim ve bunları x264 kullanarak kodlarım. Tam kod burada bulunabilir: https://gist.github.com/roxlu/6453908

+4

'da bulunabilir. Çözümünüzün bir özetini buraya ekleyebilirsiniz, böylece bağlantınızın ömrünü tamamlamış olur. – krsteeve

2

FFmpeg katedilebilen ömeği 2.8.6 bunun için bir üniforma API ortaya çıkarır olarak x264 için bir sarıcı, iyi bir fikir olarak FFpmeg Kullanılması

çoklu kodlayıcılar. Dolayısıyla, biçimleri değiştirmeye ihtiyacınız varsa, yeni bir API öğrenmek yerine yalnızca bir parametreyi değiştirebilirsiniz.

Örnek, generate_rgb tarafından oluşturulan bazı renkli çerçeveleri sentezler ve kodlar.

Çerçeve türünün (I, P, B) mümkün olduğunca az sayıda anahtar çerçeveye sahip olması (ideal olarak sadece ilk) burada açıklanmıştır: https://stackoverflow.com/a/36412909/895245 Burada belirtildiği gibi, çoğu uygulama için bunu önermiyorum.

burada çerçeve tipi kontrolü yapmak anahtar hatları

şunlardır:

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

ve:

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

sonra bir kare tipi ile kontrol edilebilir:

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

belirtildiği gibi at: https://superuser.com/questions/885452/extracting-the-index-of-key-frames-from-a-video-using-ffmpeg

Preview of generated output.

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

Derleme ve birlikte çalıştırın:

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

Ubuntu 16.04 üzerinde test edilmiştir. GitHub upstream.

+0

Downvoters lütfen açıklayın, böylece içeriği öğrenebilir ve geliştirebilirim :-) –

+0

Bu, nvcuda.dll ve onun bağımlılıkları gerektirir. Koşmak için alınamadı. – rosewater

+0

@rosewater rapor için teşekkürler. Nasıl kurulacağını öğrenirseniz bana bildirin. Sadece Ubuntu'da test edebilirim. –

İlgili konular