UFO ET IT

x264 C API를 사용하여 일련의 이미지를 H264로 인코딩하는 방법은 무엇입니까?

ufoet 2020. 12. 4. 21:30
반응형

x264 C API를 사용하여 일련의 이미지를 H264로 인코딩하는 방법은 무엇입니까?


x264 C API를 사용하여 RBG 이미지를 H264 프레임으로 인코딩하는 방법은 무엇입니까? RBG 이미지 시퀀스를 이미 만들었습니다. 이제 시퀀스를 H264 프레임 시퀀스로 변환하려면 어떻게해야합니까? 특히,이 RGB 이미지 시퀀스를 단일 초기 H264 키 프레임과 종속 H264 프레임으로 구성된 시퀀스 H264 프레임으로 어떻게 인코딩합니까?


우선 : x264.h 파일을 확인하십시오. 각 기능 및 구조에 대한 참조가 다소 포함되어 있습니다. 다운로드에서 찾을 수있는 x264.c 파일에는 샘플 구현이 포함되어 있습니다. 대부분의 사람들은 그것에 기초를 두라고 말하지만 초보자에게는 다소 복잡하다는 것을 알지만 다시 넘어가는 것이 좋은 예입니다.

먼저 x264_param_t 유형의 일부 매개 변수를 설정합니다 . 매개 변수를 설명하는 좋은 사이트는 http://mewiki.project357.com/wiki/X264_Settings 입니다. 또한 x264_param_default_preset모든 (때로는 매우 복잡한) 매개 변수를 이해할 필요없이 일부 기능을 대상으로 할 수 있는 함수를 살펴보십시오 . 또한 x264_param_apply_profile나중에 사용 합니다 ( "기준"프로필이 필요할 수 있음).

이것은 내 코드에서 몇 가지 예제 설정입니다.

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

이 후 다음과 같이 인코더를 초기화 할 수 있습니다.

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는 YUV420P 데이터를 기대합니다 (다른 일부도 추측하지만 이는 일반적인 데이터입니다). libswscale (ffmpeg에서)을 사용하여 이미지를 올바른 형식으로 변환 할 수 있습니다. 이것을 초기화하는 것은 다음과 같습니다 (24bpp의 RGB 데이터를 가정합니다).

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

인코딩은 다음과 같이 간단합니다. 각 프레임에 대해 다음을 수행하십시오.

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

나는 이것이 당신이 갈 수 있기를 바랍니다.), 나는 시작하는 데 오랜 시간을 보냈습니다. X264는 엄청나게 강력하지만 때로는 복잡한 소프트웨어입니다.

편집 : 다른 매개 변수를 사용할 때 지연된 프레임이있을 것입니다. 이것은 내 매개 변수의 경우가 아닙니다 (대부분 nolatency 옵션으로 인해). 이 경우 frame_size는 때때로 0이되며 x264_encoder_encode함수 x264_encoder_delayed_frames가 0을 반환하지 않는 한 호출 해야합니다.하지만이 기능을 사용하려면 x264.c 및 x264.h를 자세히 살펴 봐야합니다.


원시 yuv 프레임을 생성 한 다음 x264를 사용하여 인코딩하는 예제를 업로드했습니다. 전체 코드는 https://gist.github.com/roxlu/6453908 에서 찾을 수 있습니다.


FFmpeg 2.8.6 C 실행 가능 예제

FFpmeg를 x264 용 래퍼로 사용하는 것은 여러 인코더에 대해 균일 한 API를 노출하므로 좋은 생각입니다. 따라서 형식을 변경해야하는 경우 새 API를 배우는 대신 하나의 매개 변수 만 변경할 수 있습니다.

이 예제는에서 생성 된 일부 다채로운 프레임을 합성하고 인코딩합니다 generate_rgb.

가능한 한 적은 수의 키 프레임 (이상적으로는 첫 번째)을 갖도록 프레임 유형 ( I, P, B )을 제어하는 방법 은 여기에서 설명합니다. https://stackoverflow.com/a/36412909/895245 여기에서 언급했듯이 대부분의 응용 프로그램에 권장합니다.

여기에서 프레임 유형 제어를 수행하는 핵심 라인은 다음과 같습니다.

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

과:

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

그런 다음 다음을 사용하여 프레임 유형을 확인할 수 있습니다.

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

https://superuser.com/questions/885452/extracting-the-index-of-key-frames-from-a-video-using-ffmpeg 에서 언급했듯이

생성 된 출력 미리보기 .

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

다음으로 컴파일 및 실행 :

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

Ubuntu 16.04에서 테스트되었습니다. GitHub 업스트림 .

참고 URL : https://stackoverflow.com/questions/2940671/how-does-one-encode-a-series-of-images-into-h264-using-the-x264-c-api

반응형