FFmpeg API 개요

FFmpeg는 멀티미디어 데이터를 다루기 위한 강력한 라이브러리를 제공하며, 이를 통해 비디오, 오디오 인코딩 및 디코딩, 포맷 변환, 스트리밍 등을 제어할 수 있다. FFmpeg의 API는 다양한 기능을 C 언어 기반으로 제공하며, 개발자는 이 API를 통해 복잡한 멀티미디어 작업을 프로그래밍적으로 처리할 수 있다.

FFmpeg API는 크게 네 가지 주요 라이브러리로 구성된다:

  1. libavcodec: 다양한 비디오 및 오디오 코덱을 위한 라이브러리이다.
  2. libavformat: 멀티미디어 컨테이너 포맷을 처리하는 라이브러리이다.
  3. libavutil: 유틸리티 함수 및 공통 자료 구조를 제공한다.
  4. libswscale: 이미지 크기 변경, 포맷 변환 등의 작업을 처리한다.

이외에도 하드웨어 가속을 위한 libavdevice, libswresample (오디오 리샘플링), libpostproc (포스트 프로세싱) 등의 라이브러리도 존재한다.

FFmpeg 라이브러리 초기화

FFmpeg API를 사용하려면 먼저 각 라이브러리를 초기화해야 한다. 이를 위해 av_register_all()을 호출한다. 이 함수는 모든 코덱, 파일 포맷, 네트워크 지원을 초기화하는 데 사용된다.

av_register_all();

FFmpeg 라이브러리는 입력 또는 출력 컨텍스트를 기반으로 데이터를 처리한다. 파일을 열기 위해서는 먼저 AVFormatContext 구조체를 사용해야 하며, 이를 통해 파일 포맷, 코덱, 스트림 등에 대한 정보를 읽어들일 수 있다.

입력 파일 열기

파일을 열 때 avformat_open_input()을 사용하여 파일을 연결한다. 이 함수는 입력 파일의 경로와 컨텍스트를 매개변수로 받아들이다.

AVFormatContext *fmt_ctx = NULL;
avformat_open_input(&fmt_ctx, "input.mp4", NULL, NULL);

이후 파일의 스트림 정보를 얻기 위해 avformat_find_stream_info() 함수를 호출하여 파일에 포함된 오디오 및 비디오 스트림에 대한 정보를 추출한다.

avformat_find_stream_info(fmt_ctx, NULL);

코덱 정보 검색

각 스트림은 서로 다른 코덱을 사용할 수 있으며, 스트림에 연결된 코덱 정보를 얻기 위해서는 스트림의 코덱 파라미터를 탐색해야 한다. FFmpeg에서는 AVCodecParameters 구조체를 사용하여 코덱 정보를 가져올 수 있다. 스트림의 코덱을 검색하려면 먼저 해당 스트림을 선택하고, avcodec_parameters_to_context() 함수를 사용해 파라미터를 설정한다.

AVCodec *codec = avcodec_find_decoder(fmt_ctx->streams[stream_idx]->codecpar->codec_id);

코덱을 찾았다면, AVCodecContext를 할당하고 코덱에 대한 정보를 해당 컨텍스트에 채운다.

AVCodecContext *codec_ctx = avcodec_alloc_context3(codec);
avcodec_parameters_to_context(codec_ctx, fmt_ctx->streams[stream_idx]->codecpar);

이후 코덱을 열기 위해 avcodec_open2() 함수를 사용하여 코덱을 초기화한다.

avcodec_open2(codec_ctx, codec, NULL);

패킷 디코딩

FFmpeg의 디코딩 과정은 기본적으로 패킷을 읽어들인 후 이를 프레임으로 변환하는 방식으로 진행된다. 파일로부터 패킷을 읽기 위해서는 av_read_frame()을 사용한다.

AVPacket pkt;
av_read_frame(fmt_ctx, &pkt);

그 다음 패킷을 코덱에 전달하여 디코딩을 진행한다.

avcodec_send_packet(codec_ctx, &pkt);

이후 디코딩된 프레임을 수신하기 위해 avcodec_receive_frame()을 사용한다.

AVFrame *frame = av_frame_alloc();
avcodec_receive_frame(codec_ctx, frame);

메모리 관리

FFmpeg에서는 할당된 메모리를 명시적으로 해제해야 한다. 예를 들어, 디코딩에 사용한 프레임과 패킷은 각각 av_frame_free()av_packet_unref()을 사용하여 해제한다.

av_frame_free(&frame);
av_packet_unref(&pkt);

FFmpeg의 시간 축 변환

멀티미디어 데이터를 처리할 때, 서로 다른 시간 축을 가진 여러 스트림을 다루게 된다. FFmpeg에서는 AVStream 구조체에 포함된 time_base를 사용하여 시간 축을 변환할 수 있다. 시간 변환은 아래와 같은 방식으로 이루어진다:

\mathbf{T_{out}} = \frac{\mathbf{T_{in}} \times \mathbf{t_{out}}}{\mathbf{t_{in}}}

예를 들어, 입력 스트림의 시간 단위가 1/1000초이고, 출력 스트림의 시간 단위가 1/90000초인 경우, 시간 변환은 아래와 같이 수행된다:

\mathbf{T_{out}} = \frac{\mathbf{T_{in}} \times 90000}{1000}

이 계산을 통해 서로 다른 시간 단위를 가진 스트림 간의 동기화를 처리할 수 있다.

비디오 및 오디오 스트림 처리

FFmpeg API를 사용하여 비디오와 오디오 스트림을 처리하는 과정은 기본적으로 비슷하지만, 비디오 스트림은 주로 프레임을 기반으로 처리되고, 오디오 스트림은 샘플 단위로 처리된다. 각각의 스트림에 맞게 프레임 또는 샘플을 추출한 후 코덱을 통해 디코딩한다.

비디오 스트림 처리

비디오 스트림을 처리할 때, 각 프레임의 시간 정보를 기반으로 프레임을 동기화하고, 필요한 경우 프레임을 리샘플링하거나 변환한다. 비디오 스트림에서 프레임을 추출하려면 av_read_frame()으로 패킷을 읽고, 이를 디코딩하여 AVFrame 구조체에 저장한다.

비디오 스트림에서 추출된 프레임은 픽셀 포맷이 서로 다를 수 있기 때문에, sws_scale 함수를 사용하여 픽셀 포맷을 변환한다. 이를 위해 먼저 SwsContext를 초기화해야 한다.

struct SwsContext *sws_ctx = sws_getContext(
    codec_ctx->width, codec_ctx->height, codec_ctx->pix_fmt,  // 입력 해상도 및 픽셀 포맷
    codec_ctx->width, codec_ctx->height, AV_PIX_FMT_RGB24,    // 출력 해상도 및 픽셀 포맷
    SWS_BILINEAR, NULL, NULL, NULL);                         // 스케일링 방법 및 필터 설정

avcodec_receive_frame()으로 디코딩된 프레임을 받은 후, sws_scale() 함수를 사용하여 변환된 프레임을 처리한다.

sws_scale(sws_ctx, (const uint8_t *const *)frame->data, frame->linesize, 0,
          codec_ctx->height, rgb_frame->data, rgb_frame->linesize);

오디오 스트림 처리

오디오 스트림의 경우, 각 패킷은 오디오 샘플을 포함하며, 이를 디코딩하여 PCM 형식의 샘플로 변환한다. 오디오 데이터는 샘플 포맷, 샘플링 레이트, 채널 수 등에 따라 다르기 때문에 FFmpeg의 libswresample 라이브러리를 사용하여 오디오 샘플을 변환할 수 있다.

SwrContext를 초기화하여 오디오 샘플링 속도, 채널 레이아웃, 샘플 포맷 등을 변환할 수 있다.

struct SwrContext *swr_ctx = swr_alloc_set_opts(
    NULL, AV_CH_LAYOUT_STEREO, AV_SAMPLE_FMT_S16, 44100,      // 출력 포맷
    codec_ctx->channel_layout, codec_ctx->sample_fmt, codec_ctx->sample_rate, 0, NULL);
swr_init(swr_ctx);

오디오 데이터를 변환하려면 먼저 디코딩된 프레임을 swr_convert() 함수를 통해 변환한다.

swr_convert(swr_ctx, &out_samples, output_samples, frame->data, frame->nb_samples);

비디오 및 오디오 스트림의 동기화

FFmpeg에서 비디오와 오디오 스트림을 동기화하는 방법 중 하나는 PTS(Presentation Time Stamp)를 사용하는 것이다. 각 프레임이나 오디오 샘플의 PTS는 그 프레임이나 샘플이 재생될 시간 정보를 제공한다.

비디오의 경우, 각 프레임의 PTS는 다음과 같은 방식으로 계산된다:

\mathbf{PTS_{frame}} = \mathbf{PTS_{start}} + \left( \frac{\mathbf{frame\_index}}{\mathbf{frame\_rate}} \right)

오디오 샘플의 경우, PTS는 각 샘플의 수에 따라 증가하며, 오디오 샘플 레이트를 사용하여 계산된다.

\mathbf{PTS_{audio}} = \mathbf{PTS_{start}} + \left( \frac{\mathbf{sample\_index}}{\mathbf{sample\_rate}} \right)

비디오와 오디오의 동기화는 두 스트림의 PTS 값을 비교하여 결정되며, 일반적으로 오디오의 PTS를 기준으로 동기화를 맞춘다.

FFmpeg API의 확장 및 사용자 정의 필터

FFmpeg API는 다양한 필터링 기능을 제공하며, 이를 활용하여 비디오와 오디오 스트림을 처리할 수 있다. AVFilterGraph 구조체는 필터 그래프를 생성하고 관리하는 데 사용된다. 필터 그래프를 구성하려면 먼저 avfilter_graph_alloc()을 호출하여 필터 그래프를 초기화한다.

AVFilterGraph *filter_graph = avfilter_graph_alloc();

필터를 그래프에 추가하려면 avfilter_graph_create_filter() 함수를 사용한다. 예를 들어, 비디오 스트림에 대해 scale 필터를 적용하여 해상도를 변경할 수 있다.

AVFilterContext *buffersrc_ctx = NULL;
AVFilterContext *buffersink_ctx = NULL;
avfilter_graph_create_filter(&buffersrc_ctx, avfilter_get_by_name("buffer"), "in", args, NULL, filter_graph);
avfilter_graph_create_filter(&buffersink_ctx, avfilter_get_by_name("buffersink"), "out", NULL, NULL, filter_graph);

그 후, avfilter_graph_parse_ptr() 함수를 통해 필터 명령어를 적용하고, 그래프를 링크한다.

avfilter_graph_parse_ptr(filter_graph, "scale=1280:720", NULL, NULL, NULL);
avfilter_graph_config(filter_graph, NULL);

필터링된 프레임은 필터 그래프에서 처리된 후 새로운 AVFrame 구조체로 반환된다.

비디오 및 오디오 프레임의 처리 흐름

FFmpeg API에서 비디오와 오디오 데이터를 처리하는 흐름은 크게 세 단계로 나뉜다: 디코딩, 필터링, 인코딩. 디코딩된 데이터를 필터링한 후 다시 인코딩하여 파일로 출력하는 방식이다.

디코딩

디코딩은 입력 파일로부터 데이터를 추출하여 FFmpeg에서 이해할 수 있는 형식으로 변환하는 과정이다. 비디오 및 오디오 패킷을 읽고 코덱을 통해 디코딩된 후, 처리할 수 있는 프레임 형태로 변환된다.

예를 들어, 비디오 프레임을 디코딩하는 과정은 다음과 같다:

while (av_read_frame(fmt_ctx, &pkt) >= 0) {
    if (pkt.stream_index == video_stream_index) {
        avcodec_send_packet(codec_ctx, &pkt);
        avcodec_receive_frame(codec_ctx, frame);

        // 디코딩된 프레임을 필터링이나 후속 처리
    }
    av_packet_unref(&pkt);
}

필터링

디코딩된 프레임을 필터링할 때, 사용자가 설정한 필터 그래프를 통해 처리된다. 필터링 과정에서는 비디오 크기 조정, 색상 조정, 오디오 리샘플링 등 다양한 처리를 할 수 있다.

예를 들어, 비디오 스케일링 필터를 사용하여 해상도를 1280x720으로 조정할 수 있다. 필터링된 프레임은 새로운 프레임으로 반환되며, 이를 인코딩하거나 후속 처리를 진행할 수 있다.

av_buffersrc_add_frame_flags(buffersrc_ctx, frame, AV_BUFFERSRC_FLAG_KEEP_REF);
av_buffersink_get_frame(buffersink_ctx, filt_frame);

인코딩

필터링된 프레임은 다시 인코딩되어 출력 파일에 기록된다. 인코딩은 libavcodec 라이브러리의 avcodec_send_frame()avcodec_receive_packet() 함수를 사용하여 이루어진다.

avcodec_send_frame(out_codec_ctx, filt_frame);
avcodec_receive_packet(out_codec_ctx, &out_pkt);

인코딩된 패킷은 파일로 쓰기 위해 av_write_frame()을 사용하여 출력 스트림에 전달된다.

av_write_frame(out_fmt_ctx, &out_pkt);

인코딩 과정을 거치면 파일 포맷과 코덱에 맞게 압축된 형태로 저장된다.

시간 조정과 동기화

비디오와 오디오 데이터를 처리하는 동안 시간 동기화는 매우 중요하다. 비디오와 오디오가 동시에 재생될 수 있도록 하기 위해 PTS(Presentation Time Stamp)와 DTS(Decoding Time Stamp)를 적절히 관리해야 한다.

비디오와 오디오의 시간 정보는 다음 수식을 통해 동기화된다:

\mathbf{PTS_{sync}} = \frac{\mathbf{PTS_{video}} - \mathbf{PTS_{audio}}}{\mathbf{time\_base}}

이 수식을 통해 현재 프레임이 재생되어야 하는 시간을 조정하고, 오디오와 비디오가 동기화된 상태로 유지된다.

FFmpeg의 리샘플링 및 스케일링

비디오와 오디오 데이터는 다양한 샘플링 레이트와 해상도를 가질 수 있으므로, 리샘플링과 스케일링 과정이 필요하다. libswscalelibswresample 라이브러리는 각각 비디오 프레임의 스케일링과 오디오 샘플의 리샘플링을 처리한다.

오디오 리샘플링

오디오 데이터를 다른 샘플링 레이트나 포맷으로 변환할 때 swr_convert() 함수를 사용한다. 예를 들어, 44.1kHz의 오디오 데이터를 48kHz로 변환할 때 아래와 같이 코드를 작성한다:

swr_convert(swr_ctx, out_samples, output_samples, in_samples, input_samples);

비디오 스케일링

비디오 프레임의 해상도를 변환하려면 sws_scale() 함수를 사용한다. 아래는 비디오 프레임을 1920x1080 해상도로 변환하는 예이다:

sws_scale(sws_ctx, (const uint8_t *const *)frame->data, frame->linesize, 0,
          codec_ctx->height, out_frame->data, out_frame->linesize);

이 과정을 통해 다양한 해상도와 샘플링 레이트를 가진 데이터를 효율적으로 처리할 수 있다.

FFmpeg의 멀티스레드 처리

FFmpeg는 성능 향상을 위해 멀티스레드 처리를 지원한다. 멀티스레드 처리는 여러 코어를 활용하여 인코딩, 디코딩 또는 필터링 작업을 병렬로 수행할 수 있게 해준다. FFmpeg API에서 멀티스레드 처리를 활성화하기 위해서는 AVCodecContext의 스레드 관련 파라미터를 설정해야 한다.

스레드 개수 설정

스레드 개수는 AVCodecContext 구조체의 thread_count 필드를 설정하여 제어할 수 있다. 스레드 개수를 설정하면 FFmpeg가 내부적으로 여러 스레드를 생성하여 작업을 분할 처리한다.

codec_ctx->thread_count = 4;  // 4개의 스레드로 병렬 처리

스레드 처리 모드

FFmpeg는 두 가지 멀티스레드 처리 모드를 지원한다: slice 기반frame 기반. 각 모드는 thread_type 필드를 설정하여 활성화된다.

codec_ctx->thread_type = FF_THREAD_FRAME;  // frame 기반 멀티스레드 처리

멀티스레드 디코딩 예시

멀티스레드 디코딩을 활성화한 후 비디오를 디코딩하는 코드는 아래와 같다.

AVCodecContext *codec_ctx = avcodec_alloc_context3(codec);
codec_ctx->thread_count = 4;  // 4개의 스레드를 사용
codec_ctx->thread_type = FF_THREAD_FRAME;  // frame 기반 스레드 처리

// 코덱을 열고 디코딩
avcodec_open2(codec_ctx, codec, NULL);

while (av_read_frame(fmt_ctx, &pkt) >= 0) {
    avcodec_send_packet(codec_ctx, &pkt);
    avcodec_receive_frame(codec_ctx, frame);
    // 디코딩된 프레임 처리
}

이 코드에서는 4개의 스레드를 활용하여 비디오 프레임을 병렬로 처리한다. 멀티스레드 처리는 특히 고해상도 비디오나 다중 스트림 데이터를 처리할 때 성능을 크게 향상시킨다.

FFmpeg의 버퍼 관리

FFmpeg에서 멀티미디어 데이터를 처리할 때, 입력과 출력 스트림 사이에서 데이터를 효율적으로 관리하기 위해 버퍼가 사용된다. 특히, 실시간 스트리밍이나 대용량 파일을 처리할 때는 버퍼 관리가 중요하다. FFmpeg의 버퍼는 AVBufferRefAVFrame 구조체를 통해 관리된다.

AVBufferRef 구조체

AVBufferRef는 데이터를 참조하는 버퍼이다. 이 구조체는 메모리를 효율적으로 관리할 수 있도록 참조 카운트를 지원하며, 메모리 누수를 방지할 수 있다. 버퍼는 직접 할당하여 사용할 수 있다.

AVBufferRef *buffer = av_buffer_alloc(size);

할당된 버퍼는 사용 후 반드시 해제해야 한다.

av_buffer_unref(&buffer);

AVFrame과 AVBufferRef

AVFrame은 비디오와 오디오 프레임 데이터를 저장하는 구조체로, 내부적으로 AVBufferRef를 사용하여 데이터를 참조한다. AVFrame을 사용하여 프레임 데이터를 저장하고 처리할 수 있다.

AVFrame *frame = av_frame_alloc();
frame->buf[0] = av_buffer_alloc(size);  // 버퍼를 할당하여 프레임에 연결

프레임의 데이터를 처리한 후에는 프레임과 버퍼를 해제한다.

av_frame_unref(frame);

버퍼 복사 및 공유

FFmpeg에서 데이터 버퍼를 복사하거나 공유해야 할 때 av_buffer_ref() 함수와 av_buffer_unref() 함수를 사용하여 참조를 관리할 수 있다. 예를 들어, 동일한 데이터를 여러 개의 스레드에서 참조할 때 버퍼 복사를 최소화하고, 참조 카운트를 활용하여 메모리를 효율적으로 관리한다.

AVBufferRef *new_buffer = av_buffer_ref(buffer);  // 버퍼 참조 추가
av_buffer_unref(&new_buffer);  // 참조 해제

FFmpeg API에서 오류 처리

FFmpeg API를 사용할 때는 다양한 오류가 발생할 수 있다. API 호출이 성공하면 0을 반환하고, 실패하면 음수 값을 반환한다. 이러한 오류 코드를 적절히 처리하는 것이 중요하다.

오류 코드의 처리

오류가 발생하면 FFmpeg는 음수 값을 반환하며, 이 값은 오류의 종류를 나타낸다. 오류 코드는 AVERROR_* 매크로로 정의되어 있다. 예를 들어, 파일을 여는 도중 오류가 발생하면 AVERROR(ENOENT)가 반환된다.

int ret = avformat_open_input(&fmt_ctx, "input.mp4", NULL, NULL);
if (ret < 0) {
    fprintf(stderr, "파일을 열 수 없다: %s\n", av_err2str(ret));
}

오류 메시지 출력

FFmpeg는 오류 메시지를 출력할 때 av_err2str() 함수를 사용하여 오류 코드를 문자열로 변환할 수 있다.

char err_buf[AV_ERROR_MAX_STRING_SIZE];
av_strerror(ret, err_buf, sizeof(err_buf));
fprintf(stderr, "오류 발생: %s\n", err_buf);

이 코드를 통해 FFmpeg API 호출에서 발생한 오류를 쉽게 디버깅할 수 있다.

FFmpeg API의 메모리 관리

FFmpeg API에서 메모리를 효율적으로 관리하는 것은 매우 중요하다. 특히, 다량의 멀티미디어 데이터를 처리하는 경우 메모리 누수를 방지하고 적절한 자원 해제가 필수적이다.

AVFrame의 메모리 할당 및 해제

비디오나 오디오 프레임을 처리할 때, AVFrame 구조체를 사용하여 각 프레임의 데이터를 저장한다. AVFrame은 동적 메모리를 할당받기 때문에, 사용 후 반드시 해제해야 한다. 프레임은 av_frame_alloc()으로 생성되고, 작업이 완료되면 av_frame_free()를 사용하여 해제된다.

AVFrame *frame = av_frame_alloc();
if (!frame) {
    fprintf(stderr, "프레임 메모리 할당 실패\n");
}

// 작업 후 프레임 메모리 해제
av_frame_free(&frame);

AVPacket의 메모리 할당 및 해제

AVPacket 구조체는 비디오 또는 오디오의 압축된 데이터를 담고 있다. 패킷은 av_packet_alloc() 함수를 통해 할당되며, 사용 후 av_packet_unref() 또는 av_packet_free()를 통해 메모리를 해제한다.

AVPacket *pkt = av_packet_alloc();
if (!pkt) {
    fprintf(stderr, "패킷 메모리 할당 실패\n");
}

// 패킷 데이터 처리 후 해제
av_packet_unref(pkt);  // 패킷 내의 데이터 해제
av_packet_free(&pkt);  // 패킷 구조체 자체 해제

컨텍스트 메모리 관리

FFmpeg에서 사용하는 주요 컨텍스트는 AVFormatContext, AVCodecContext, AVFilterContext 등이다. 이 컨텍스트들도 동적으로 할당되며, 사용이 끝나면 반드시 해제해야 한다.

예를 들어, AVFormatContextavformat_open_input()으로 할당되며, avformat_close_input()으로 해제된다.

AVFormatContext *fmt_ctx = NULL;
avformat_open_input(&fmt_ctx, "input.mp4", NULL, NULL);

// 작업 후 컨텍스트 해제
avformat_close_input(&fmt_ctx);

AVCodecContext는 코덱을 할당하고 열 때 사용되며, 작업 후에는 avcodec_free_context()를 통해 메모리를 해제한다.

AVCodecContext *codec_ctx = avcodec_alloc_context3(codec);
if (!codec_ctx) {
    fprintf(stderr, "코덱 컨텍스트 할당 실패\n");
}

// 코덱 해제
avcodec_free_context(&codec_ctx);

AVFilterContext의 메모리 해제

필터 처리에 사용하는 AVFilterGraphAVFilterContext도 작업이 끝난 후에는 반드시 해제해야 한다. 필터 그래프는 avfilter_graph_alloc()으로 생성되며, 작업 후에는 avfilter_graph_free()로 해제된다.

AVFilterGraph *filter_graph = avfilter_graph_alloc();
// 필터 처리 후 해제
avfilter_graph_free(&filter_graph);

FFmpeg의 파일 출력

FFmpeg는 다양한 파일 포맷을 지원하며, 출력 파일을 생성하고 스트림을 기록하는 기능을 제공한다. 출력 파일을 처리하려면 먼저 AVFormatContext를 생성하고, 파일 포맷에 맞게 설정해야 한다.

출력 파일 생성

출력 파일을 생성하기 위해서는 avformat_alloc_output_context2()를 사용하여 파일 컨텍스트를 할당하고, 원하는 출력 포맷을 지정한다.

AVFormatContext *out_fmt_ctx = NULL;
avformat_alloc_output_context2(&out_fmt_ctx, NULL, NULL, "output.mp4");
if (!out_fmt_ctx) {
    fprintf(stderr, "출력 포맷 컨텍스트 할당 실패\n");
}

출력 스트림 생성

비디오 또는 오디오 스트림을 출력 파일에 기록하기 위해서는 스트림을 생성하고, 각 스트림에 맞는 코덱을 설정해야 한다. AVStream 구조체를 사용하여 스트림을 생성한다.

AVStream *out_stream = avformat_new_stream(out_fmt_ctx, NULL);
if (!out_stream) {
    fprintf(stderr, "출력 스트림 생성 실패\n");
}

각 스트림에 대해 코덱 설정을 지정하고, 스트림의 파라미터를 설정한 후 인코딩된 데이터를 파일로 기록한다.

파일로 패킷 기록

인코딩된 패킷을 출력 파일에 기록하려면, av_write_frame()을 사용하여 패킷을 출력 스트림으로 보낸다. 이때 패킷의 타임스탬프를 올바르게 설정하여 동기화된 파일이 생성되도록 해야 한다.

int ret = av_write_frame(out_fmt_ctx, &pkt);
if (ret < 0) {
    fprintf(stderr, "패킷 기록 실패: %s\n", av_err2str(ret));
}

출력 파일 닫기

출력 파일을 다 기록한 후에는 반드시 av_write_trailer()를 호출하여 파일의 트레일러를 기록한 뒤, 파일을 닫아야 한다. 이렇게 하면 모든 스트림이 올바르게 종료되고, 파일 포맷에 맞는 종결 정보가 저장된다.

av_write_trailer(out_fmt_ctx);

파일이 닫힌 후에는 출력 컨텍스트를 해제하여 메모리를 반환해야 한다.

avformat_free_context(out_fmt_ctx);

FFmpeg의 디버깅과 로깅

FFmpeg는 API 사용 중 발생하는 문제를 해결하기 위해 로깅 및 디버깅 기능을 제공한다. FFmpeg의 로깅 레벨을 설정하여 프로그램 실행 중 발생하는 오류나 경고를 출력할 수 있다.

로깅 레벨 설정

FFmpeg의 로깅 레벨은 av_log_set_level() 함수를 통해 설정할 수 있으며, 다양한 레벨의 로그를 출력할 수 있다. 예를 들어, AV_LOG_ERROR 수준의 로그만 출력하도록 설정하려면 아래와 같이 작성한다.

av_log_set_level(AV_LOG_ERROR);

로깅 레벨은 아래와 같은 옵션을 제공한다:

사용자 정의 로깅 함수

FFmpeg는 사용자 정의 로깅 함수를 설정할 수 있는 기능도 제공한다. av_log_set_callback() 함수를 통해 사용자 정의 로깅 함수를 설정할 수 있으며, 이를 통해 FFmpeg의 로그 출력을 제어할 수 있다.

void custom_log(void *ptr, int level, const char *fmt, va_list args) {
    vfprintf(stderr, fmt, args);  // 사용자 정의 로그 출력
}

av_log_set_callback(custom_log);

사용자 정의 함수는 FFmpeg에서 발생한 로그 메시지를 특정 방식으로 출력하거나 저장하는 데 유용하다.

FFmpeg의 스트리밍 처리

FFmpeg는 네트워크를 통한 멀티미디어 데이터의 스트리밍을 지원하며, 다양한 프로토콜을 사용하여 실시간 스트리밍 데이터를 처리할 수 있다. RTMP, HTTP, HLS 등의 프로토콜을 사용하여 비디오와 오디오 데이터를 실시간으로 전송하거나 받을 수 있다.

입력 스트림 처리

입력 스트림을 처리하기 위해 FFmpeg는 avformat_open_input()을 사용하여 네트워크 스트림을 입력으로 연결할 수 있다. 예를 들어, RTMP 스트림을 처리하려면 스트림의 URL을 입력 파일 경로로 지정한다.

AVFormatContext *fmt_ctx = NULL;
avformat_open_input(&fmt_ctx, "rtmp://example.com/live/stream", NULL, NULL);

스트림 정보를 추출하기 위해 avformat_find_stream_info() 함수를 호출하여 해당 스트림의 비디오 및 오디오 정보를 읽어온다.

avformat_find_stream_info(fmt_ctx, NULL);

이후 일반 파일 처리와 동일하게 패킷을 읽고, 디코딩을 통해 비디오 및 오디오 데이터를 처리할 수 있다.

출력 스트림 설정

출력 스트림을 설정하기 위해서는 avformat_alloc_output_context2()을 사용하여 출력 컨텍스트를 생성하고, 스트리밍 프로토콜에 맞는 URL을 지정한다. 예를 들어, RTMP 스트림으로 데이터를 전송하려면 RTMP URL을 설정한다.

AVFormatContext *out_fmt_ctx = NULL;
avformat_alloc_output_context2(&out_fmt_ctx, NULL, "flv", "rtmp://example.com/live/stream");

스트림 초기화 및 연결

출력 스트림을 생성한 후, 스트림을 초기화하고 avio_open()을 사용하여 출력 URL을 FFmpeg의 IO 컨텍스트에 연결한다. 이렇게 하면 FFmpeg가 네트워크 스트림으로 데이터를 전송할 수 있게 된다.

avio_open(&out_fmt_ctx->pb, "rtmp://example.com/live/stream", AVIO_FLAG_WRITE);

스트림이 연결되면 비디오와 오디오 스트림을 인코딩하여 출력 스트림으로 전송할 수 있다. 인코딩된 패킷은 av_write_frame()을 통해 스트림으로 전송된다.

av_write_frame(out_fmt_ctx, &pkt);

스트리밍 종료

스트리밍 작업이 완료되면 av_write_trailer()를 호출하여 스트림을 종료하고, 모든 스트림 데이터를 전송한 후 avio_closep()를 사용하여 스트림을 닫습니다.

av_write_trailer(out_fmt_ctx);
avio_closep(&out_fmt_ctx->pb);

이 과정을 통해 FFmpeg는 네트워크를 통한 실시간 스트리밍이 가능해진다.

라이브 스트리밍 변환

FFmpeg는 라이브 스트리밍 데이터를 인코딩 및 변환하여 실시간으로 전송할 수 있는 기능을 제공한다. 예를 들어, 입력 스트림을 특정 포맷으로 변환하여 실시간으로 RTMP 서버에 전송할 수 있다.

입력 스트림 설정

라이브 스트리밍을 처리하려면 입력으로 네트워크 스트림을 받아들여야 한다. 이를 위해 avformat_open_input()을 사용하여 입력 스트림을 열고, 스트림 정보를 파싱한다.

AVFormatContext *in_fmt_ctx = NULL;
avformat_open_input(&in_fmt_ctx, "rtmp://example.com/live/stream", NULL, NULL);
avformat_find_stream_info(in_fmt_ctx, NULL);

출력 스트림 설정

출력 스트림은 avformat_alloc_output_context2()로 생성하고, RTMP URL을 설정하여 스트림을 전송할 서버를 지정한다.

AVFormatContext *out_fmt_ctx = NULL;
avformat_alloc_output_context2(&out_fmt_ctx, NULL, "flv", "rtmp://example.com/live/output");
avio_open(&out_fmt_ctx->pb, "rtmp://example.com/live/output", AVIO_FLAG_WRITE);

스트림 복사 및 전송

라이브 스트리밍을 변환할 때는 입력 스트림에서 읽어들인 패킷을 변환한 후, 출력 스트림으로 전송한다. 패킷을 읽고, 필요에 따라 인코딩을 거쳐 av_write_frame()으로 출력한다.

while (av_read_frame(in_fmt_ctx, &pkt) >= 0) {
    av_interleaved_write_frame(out_fmt_ctx, &pkt);
    av_packet_unref(&pkt);
}

라이브 스트리밍 작업이 완료되면 av_write_trailer()로 스트림을 종료하고, 스트림을 닫습니다.

av_write_trailer(out_fmt_ctx);
avio_closep(&out_fmt_ctx->pb);

스트리밍 중 비트레이트 조정

FFmpeg는 스트리밍 중에 동적으로 비트레이트를 조정할 수 있는 기능을 제공한다. 이는 네트워크 대역폭이나 사용자 환경에 따라 적절한 품질로 스트리밍을 제공하기 위한 중요한 기능이다. 비트레이트 조정은 AVCodecContext에서 설정할 수 있다.

codec_ctx->bit_rate = 1000000;  // 비트레이트를 1Mbps로 설정

비트레이트를 변경함으로써 스트리밍 중에 데이터 전송량과 품질 간의 균형을 조정할 수 있다.

스트리밍 중 다중 트랙 지원

FFmpeg는 하나의 스트림에서 여러 개의 트랙을 지원할 수 있다. 예를 들어, 비디오, 오디오, 자막 트랙을 동시에 포함하는 스트림을 생성하거나 전송할 수 있다. 다중 트랙을 지원하기 위해서는 각 트랙에 대해 별도의 AVStream을 생성하고, 각 트랙의 코덱 설정을 개별적으로 관리해야 한다.

다중 트랙 스트림 생성

먼저 출력 컨텍스트를 생성하고, 비디오와 오디오 트랙을 추가한다. 각 트랙은 avformat_new_stream() 함수를 사용하여 생성한다.

AVStream *video_stream = avformat_new_stream(out_fmt_ctx, NULL);
AVStream *audio_stream = avformat_new_stream(out_fmt_ctx, NULL);
if (!video_stream || !audio_stream) {
    fprintf(stderr, "트랙 생성 실패\n");
}

각 스트림에 맞는 코덱을 설정한 후, 스트림을 초기화한다.

AVCodecContext *video_codec_ctx = avcodec_alloc_context3(video_codec);
AVCodecContext *audio_codec_ctx = avcodec_alloc_context3(audio_codec);

// 비디오 및 오디오 코덱 설정
video_codec_ctx->bit_rate = 400000;
audio_codec_ctx->bit_rate = 128000;

트랙별 데이터 처리

다중 트랙 스트림에서 각 트랙은 별도로 패킷을 읽고 처리한다. 비디오와 오디오 패킷은 각각의 코덱 컨텍스트에서 디코딩되고, 변환된 후 출력 스트림에 기록된다. 패킷을 처리할 때는 트랙의 stream_index를 확인하여 비디오 패킷인지 오디오 패킷인지를 구분한다.

AVPacket pkt;
while (av_read_frame(in_fmt_ctx, &pkt) >= 0) {
    if (pkt.stream_index == video_stream_idx) {
        // 비디오 패킷 처리
    } else if (pkt.stream_index == audio_stream_idx) {
        // 오디오 패킷 처리
    }
    av_packet_unref(&pkt);
}

다중 트랙 동기화

다중 트랙 스트림에서 비디오와 오디오의 동기화는 매우 중요하다. 각 트랙의 PTS(Presentation Time Stamp)를 기반으로 동기화가 이루어진다. 이를 위해 각 트랙의 PTS를 적절히 관리하여 시간 차이가 발생하지 않도록 한다.

비디오와 오디오의 PTS를 비교하여 적절한 동기화를 유지할 수 있으며, FFmpeg는 내부적으로 av_interleaved_write_frame() 함수를 사용하여 두 트랙의 동기화를 자동으로 처리한다.

av_interleaved_write_frame(out_fmt_ctx, &pkt);

이 함수는 비디오와 오디오의 PTS를 자동으로 관리하여 시간에 맞춰 데이터를 출력한다.

FFmpeg의 비디오 및 오디오 필터링

FFmpeg는 다양한 필터를 제공하여 비디오와 오디오 데이터를 처리할 수 있다. 필터는 영상이나 음성을 변환, 편집, 조정하는 데 사용되며, libavfilter 라이브러리를 통해 접근할 수 있다. 비디오 필터는 해상도 변경, 색상 조정, 자막 추가 등이 있으며, 오디오 필터는 볼륨 조정, 리샘플링 등이 있다.

비디오 필터 적용

비디오 필터를 적용하려면 먼저 AVFilterGraph를 생성하고 필터 그래프를 구성해야 한다. 예를 들어, 비디오 해상도를 1280x720으로 변경하는 필터를 적용하려면 아래와 같은 단계를 따른다.

  1. 필터 그래프와 컨텍스트를 초기화한다.
AVFilterGraph *filter_graph = avfilter_graph_alloc();
AVFilterContext *buffersrc_ctx = NULL;
AVFilterContext *buffersink_ctx = NULL;
  1. buffersrcbuffersink 필터를 추가하고, 스케일 필터를 설정한다.
avfilter_graph_create_filter(&buffersrc_ctx, avfilter_get_by_name("buffer"), "in", NULL, NULL, filter_graph);
avfilter_graph_create_filter(&buffersink_ctx, avfilter_get_by_name("buffersink"), "out", NULL, NULL, filter_graph);

avfilter_graph_parse_ptr(filter_graph, "scale=1280:720", NULL, NULL, NULL);
avfilter_graph_config(filter_graph, NULL);
  1. 필터링된 프레임을 sws_scale() 함수를 통해 처리한다.
av_buffersrc_add_frame_flags(buffersrc_ctx, frame, AV_BUFFERSRC_FLAG_KEEP_REF);
av_buffersink_get_frame(buffersink_ctx, filt_frame);

오디오 필터 적용

오디오 필터는 오디오 샘플의 볼륨 조정, 리샘플링, 채널 레이아웃 변경 등을 지원한다. 예를 들어, 오디오 볼륨을 1.5배로 증가시키는 필터를 적용하려면 아래와 같이 설정한다.

  1. 필터 그래프와 컨텍스트를 초기화한다.
AVFilterGraph *audio_filter_graph = avfilter_graph_alloc();
AVFilterContext *abuffersrc_ctx = NULL;
AVFilterContext *abuffersink_ctx = NULL;
  1. abuffersrcabuffersink 필터를 추가하고, 볼륨 필터를 설정한다.
avfilter_graph_create_filter(&abuffersrc_ctx, avfilter_get_by_name("abuffer"), "in", NULL, NULL, audio_filter_graph);
avfilter_graph_create_filter(&abuffersink_ctx, avfilter_get_by_name("abuffersink"), "out", NULL, NULL, audio_filter_graph);

avfilter_graph_parse_ptr(audio_filter_graph, "volume=1.5", NULL, NULL, NULL);
avfilter_graph_config(audio_filter_graph, NULL);
  1. 필터링된 오디오 샘플을 처리한다.
av_buffersrc_add_frame_flags(abuffersrc_ctx, frame, AV_BUFFERSRC_FLAG_KEEP_REF);
av_buffersink_get_frame(abuffersink_ctx, filt_frame);

이 과정을 통해 FFmpeg는 비디오와 오디오 스트림을 실시간으로 필터링하고, 변환할 수 있다.

FFmpeg의 GPU 가속

FFmpeg는 GPU를 활용한 인코딩 및 디코딩을 지원하여 성능을 향상시킬 수 있다. 특히, 대용량 비디오 데이터를 처리할 때 GPU 가속을 통해 더 빠른 속도로 작업을 처리할 수 있다. FFmpeg는 NVIDIA의 NVENC, AMD의 AMF, Intel의 QSV 등의 하드웨어 인코딩을 지원한다.

GPU 인코딩 활성화

NVIDIA NVENC를 사용하는 GPU 인코딩을 활성화하려면 AVCodec을 설정할 때 h264_nvenc 코덱을 사용하여 GPU에서 비디오 인코딩을 수행할 수 있다.

AVCodec *codec = avcodec_find_encoder_by_name("h264_nvenc");
AVCodecContext *codec_ctx = avcodec_alloc_context3(codec);

인코딩 설정을 완료한 후, GPU에서 인코딩 작업이 이루어지도록 패킷을 처리한다.

GPU 디코딩 활성화

GPU 디코딩은 비디오를 GPU에서 해석하여 CPU의 부하를 줄일 수 있다. NVIDIA의 경우, cuvid를 사용하여 GPU에서 비디오를 디코딩할 수 있다.

AVCodec *decoder = avcodec_find_decoder_by_name("h264_cuvid");
AVCodecContext *dec_ctx = avcodec_alloc_context3(decoder);

이후 디코딩된 데이터를 처리하는 것은 일반적인 FFmpeg의 디코딩 흐름과 동일하게 진행된다.

FFmpeg의 다중 파일 처리

FFmpeg는 여러 개의 파일을 동시에 처리할 수 있는 기능을 제공한다. 이를 통해 여러 입력 파일을 병합하거나, 여러 출력 파일로 변환 작업을 병렬로 처리할 수 있다. 여러 파일을 처리할 때는 AVFormatContext를 각각의 파일에 대해 생성하고, 각 파일의 스트림을 관리해야 한다.

여러 입력 파일 병합

여러 개의 입력 파일을 하나의 출력 파일로 병합할 때는 각 입력 파일에 대해 AVFormatContext를 열어야 한다. 이후 각 파일의 스트림을 하나의 출력 파일로 전송하여 병합 작업을 수행한다.

  1. 여러 입력 파일 열기

먼저 각 입력 파일을 avformat_open_input()을 통해 열고, 스트림 정보를 추출한다.

AVFormatContext *fmt_ctx1 = NULL;
AVFormatContext *fmt_ctx2 = NULL;
avformat_open_input(&fmt_ctx1, "input1.mp4", NULL, NULL);
avformat_open_input(&fmt_ctx2, "input2.mp4", NULL, NULL);

avformat_find_stream_info(fmt_ctx1, NULL);
avformat_find_stream_info(fmt_ctx2, NULL);
  1. 출력 파일 설정

출력 파일을 생성하고, 각 입력 파일의 스트림을 출력 스트림에 추가한다.

AVFormatContext *out_fmt_ctx = NULL;
avformat_alloc_output_context2(&out_fmt_ctx, NULL, NULL, "output.mp4");

AVStream *out_stream1 = avformat_new_stream(out_fmt_ctx, NULL);
AVStream *out_stream2 = avformat_new_stream(out_fmt_ctx, NULL);
  1. 스트림 복사 및 병합

각 입력 파일에서 읽어온 패킷을 출력 스트림에 기록하여 파일을 병합한다.

AVPacket pkt;
while (av_read_frame(fmt_ctx1, &pkt) >= 0) {
    av_interleaved_write_frame(out_fmt_ctx, &pkt);
    av_packet_unref(&pkt);
}

while (av_read_frame(fmt_ctx2, &pkt) >= 0) {
    av_interleaved_write_frame(out_fmt_ctx, &pkt);
    av_packet_unref(&pkt);
}
  1. 파일 종료 및 정리

모든 파일이 병합된 후, 출력 파일의 트레일러를 기록하고 파일을 닫습니다.

av_write_trailer(out_fmt_ctx);
avio_closep(&out_fmt_ctx->pb);
avformat_close_input(&fmt_ctx1);
avformat_close_input(&fmt_ctx2);

파일 분할

FFmpeg는 큰 파일을 여러 개의 작은 파일로 분할하는 기능도 제공한다. 이 과정에서 지정한 크기 또는 시간 단위로 파일을 분할할 수 있다. 예를 들어, 비디오 파일을 10초 단위로 분할하려면 segment 옵션을 사용할 수 있다.

  1. 출력 파일 세그먼트 설정

분할된 출력 파일을 기록할 때는 FFmpeg의 세그먼트 옵션을 설정하여 일정한 시간 간격으로 파일을 나눈다.

AVFormatContext *out_fmt_ctx = NULL;
avformat_alloc_output_context2(&out_fmt_ctx, NULL, NULL, "output_segment_%03d.mp4");

여기서 %03d는 분할된 파일의 이름에 순차적인 숫자를 붙이기 위한 형식 지정자이다.

  1. 시간 간격에 따른 분할

출력 파일을 시간 간격으로 분할할 때는 segment_time 옵션을 설정하여 각 파일의 길이를 제어할 수 있다.

int segment_time = 10;  // 10초 단위로 파일 분할
  1. 패킷 처리 및 파일 분할

스트림을 읽으면서 지정된 시간 간격에 따라 파일을 분할하고 기록한다.

int64_t next_segment_time = segment_time * AV_TIME_BASE;
while (av_read_frame(fmt_ctx, &pkt) >= 0) {
    if (pkt.pts >= next_segment_time) {
        av_write_trailer(out_fmt_ctx);
        avformat_alloc_output_context2(&out_fmt_ctx, NULL, NULL, "output_segment_%03d.mp4");
        next_segment_time += segment_time * AV_TIME_BASE;
    }
    av_interleaved_write_frame(out_fmt_ctx, &pkt);
    av_packet_unref(&pkt);
}
  1. 파일 종료

분할 작업이 완료되면 각 분할된 파일에 대해 트레일러를 기록하고, 모든 파일을 닫습니다.

av_write_trailer(out_fmt_ctx);
avio_closep(&out_fmt_ctx->pb);
avformat_close_input(&fmt_ctx);

파일 길이 조정

FFmpeg는 비디오나 오디오 파일의 길이를 조정하여 특정 구간만 추출하거나, 원하는 시간에 맞게 파일을 자를 수 있다. 이 작업은 파일을 읽고, 시작 시간과 종료 시간을 설정하여 해당 구간만 처리하는 방식으로 이루어진다.

  1. 파일 구간 추출

파일에서 특정 시간대의 데이터를 추출하기 위해 AVFormatContext에서 시작 시간과 종료 시간을 설정할 수 있다.

int64_t start_time = 30 * AV_TIME_BASE;  // 30초에서 시작
int64_t duration = 10 * AV_TIME_BASE;    // 10초 구간만 추출

av_seek_frame(fmt_ctx, -1, start_time, AVSEEK_FLAG_ANY);
  1. 구간 내 패킷 처리

지정된 구간 내에서 패킷을 읽어와 출력 파일에 기록한다.

while (av_read_frame(fmt_ctx, &pkt) >= 0) {
    if (pkt.pts > start_time + duration) {
        break;
    }
    av_interleaved_write_frame(out_fmt_ctx, &pkt);
    av_packet_unref(&pkt);
}

파일 변환 중 오류 처리

FFmpeg API를 사용할 때, 파일 변환 과정에서 다양한 오류가 발생할 수 있다. 이러한 오류를 처리하기 위해서는 적절한 오류 메시지를 출력하고, 오류가 발생한 상황에 맞는 예외 처리가 필요하다.

오류 코드 확인

FFmpeg의 각 함수는 성공 시 0을 반환하고, 실패 시 음수 값을 반환한다. 반환된 오류 코드를 확인하여 문제가 발생했을 때 적절한 조치를 취할 수 있다.

int ret = avformat_open_input(&fmt_ctx, "input.mp4", NULL, NULL);
if (ret < 0) {
    fprintf(stderr, "파일을 열 수 없다: %s\n", av_err2str(ret));
}

오류 메시지 출력

FFmpeg는 오류 코드를 사람이 읽을 수 있는 오류 메시지로 변환하기 위한 av_strerror() 함수를 제공한다. 이 함수를 사용하여 오류 메시지를 출력할 수 있다.

char err_buf[AV_ERROR_MAX_STRING_SIZE];
av_strerror(ret, err_buf, sizeof(err_buf));
fprintf(stderr, "오류 발생: %s\n", err_buf);

이 방식으로 각 함수의 호출 결과를 확인하고, 오류가 발생했을 때 즉시 대응할 수 있다.