Ubuntu で libav を使ってみる
ここでいう libav とは,FFmpeg が提供する libavcodec(符号化・復号),libavformat(コンテナの入出力),libswscale(画素形式の変換・拡大縮小),libavutil(共通機能)などのライブラリの総称である。これらを使って,動画ファイルを読み込み,フレームを復号し,画像ファイルとして保存するプログラムを作る手順を示す。
開発用パッケージのインストール
FFmpeg のライブラリを使うプログラムをビルドするには,ヘッダファイルを含む開発用パッケージが必要である。インストールするには,端末で,次のコマンドを実行する。
sudo apt update
sudo apt -y install build-essential pkg-config ffmpeg \
libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libswresample-dev libsdl2-dev
libsdl2-dev は,復号したフレームを画面に表示するプログラムで使用する SDL2 の開発用パッケージである。インストールされている FFmpeg のバージョンを確認するには,端末で,次のコマンドを実行する。
ffmpeg -version
pkg-config --modversion libavcodec libavformat libswscale
API に関する注意
Web 上には,dranger.com の "An ffmpeg and SDL Tutorial"(tutorial01.c, tutorial02.c など)をはじめとする古い解説が多数ある。しかし,これらが使用している API の多くは FFmpeg 5.0 で削除済みであり,Ubuntu 22.04 (FFmpeg 4.4) 以降,特に Ubuntu 24.04 (FFmpeg 6.1) ではそのままではコンパイルできない。主な対応関係は次の通りである。
-
av_open_input_file→ avformat_open_input -
CODEC_TYPE_VIDEO→ AVMEDIA_TYPE_VIDEO -
PIX_FMT_RGB24,PIX_FMT_YUV420P→ AV_PIX_FMT_RGB24, AV_PIX_FMT_YUV420P -
AVStream::codec(AVCodecContext)→ AVStream::codecpar(AVCodecParameters)をavcodec_alloc_context3とavcodec_parameters_to_contextで AVCodecContext に変換 -
avcodec_open→ avcodec_open2 -
avcodec_decode_video,avcodec_decode_video2→ avcodec_send_packet と avcodec_receive_frame の組み合わせ -
img_convert,AVPicture,avpicture_fill→ sws_getContext と sws_scale,バッファ確保はav_frame_get_bufferまたはav_image_alloc -
SDL_Overlay(SDL 1.2)→ SDL_Texture(SDL2,SDL_PIXELFORMAT_IYUVなどを使用)
動画のフレームを PPM ファイルに保存するプログラム
- プログラムの作成
テキストエディタで
decode_ppm.cという名前のファイルを作成し,次の内容を保存する。動画ファイルの先頭から 5 フレームを復号し,RGB24 に変換してframe1.ppm~frame5.ppmに保存するプログラムである。#include <stdio.h> #include <libavcodec/avcodec.h> #include <libavformat/avformat.h> #include <libswscale/swscale.h> #define MAX_FRAMES 5 static void save_frame(const AVFrame *frame, int width, int height, int index) { char filename[64]; snprintf(filename, sizeof(filename), "frame%d.ppm", index); FILE *fp = fopen(filename, "wb"); if (fp == NULL) return; fprintf(fp, "P6\n%d %d\n255\n", width, height); for (int y = 0; y < height; y++) { fwrite(frame->data[0] + y * frame->linesize[0], 1, width * 3, fp); } fclose(fp); } int main(int argc, char *argv[]) { if (argc < 2) { fprintf(stderr, "usage: %s <video file>\n", argv[0]); return 1; } /* コンテナを開き,ストリーム情報を取得 */ AVFormatContext *fmt_ctx = NULL; if (avformat_open_input(&fmt_ctx, argv[1], NULL, NULL) < 0) return 1; if (avformat_find_stream_info(fmt_ctx, NULL) < 0) return 1; av_dump_format(fmt_ctx, 0, argv[1], 0); /* ビデオストリームとデコーダを選択 */ const AVCodec *codec = NULL; int video_index = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, &codec, 0); if (video_index < 0) return 1; /* AVCodecParameters から AVCodecContext を作り,デコーダを開く */ AVCodecContext *dec_ctx = avcodec_alloc_context3(codec); avcodec_parameters_to_context(dec_ctx, fmt_ctx->streams[video_index]->codecpar); if (avcodec_open2(dec_ctx, codec, NULL) < 0) return 1; AVFrame *frame = av_frame_alloc(); AVFrame *rgb = av_frame_alloc(); rgb->format = AV_PIX_FMT_RGB24; rgb->width = dec_ctx->width; rgb->height = dec_ctx->height; av_frame_get_buffer(rgb, 0); struct SwsContext *sws_ctx = sws_getContext( dec_ctx->width, dec_ctx->height, dec_ctx->pix_fmt, dec_ctx->width, dec_ctx->height, AV_PIX_FMT_RGB24, SWS_BILINEAR, NULL, NULL, NULL); AVPacket *pkt = av_packet_alloc(); int saved = 0; while (saved < MAX_FRAMES && av_read_frame(fmt_ctx, pkt) >= 0) { if (pkt->stream_index == video_index && avcodec_send_packet(dec_ctx, pkt) == 0) { while (saved < MAX_FRAMES && avcodec_receive_frame(dec_ctx, frame) == 0) { sws_scale(sws_ctx, (const uint8_t * const *)frame->data, frame->linesize, 0, dec_ctx->height, rgb->data, rgb->linesize); save_frame(rgb, dec_ctx->width, dec_ctx->height, ++saved); } } av_packet_unref(pkt); } av_packet_free(&pkt); sws_freeContext(sws_ctx); av_frame_free(&rgb); av_frame_free(&frame); avcodec_free_context(&dec_ctx); avformat_close_input(&fmt_ctx); printf("%d frames saved\n", saved); return 0; } - コンパイル
端末で,次のコマンドを実行する。pkg-config を使うことで,ヘッダファイルとライブラリの指定が自動的に行われる。
gcc -o decode_ppm decode_ppm.c $(pkg-config --cflags --libs libavformat libavcodec libswscale libavutil) - 実行
動画ファイルを用意し,端末で,次のコマンドを実行する。動画ファイルが手元にない場合は,1 行目のコマンドで FFmpeg にテスト用の動画を生成させることができる。
ffmpeg -f lavfi -i testsrc=size=640x480:rate=30:duration=5 -pix_fmt yuv420p test.mp4 ./decode_ppm test.mp4実行すると,frame1.ppm, frame2.ppm, frame3.ppm, frame4.ppm, frame5.ppm ができる。生成された画像を確認するには,端末で,次のコマンドを実行する。
ls -l frame*.ppm xdg-open frame1.ppm
画面表示を行うプログラム
復号したフレームを画面に表示するには,SDL2 のテクスチャに YUV データを転送する。音声との同期を含む動画プレイヤーの実装例は,FFmpeg の新しい API に対応した次のリポジトリで公開されている。
- https://github.com/leandromoreira/ffmpeg-libav-tutorial(libav* の基本的な使い方の解説とサンプルコード)
- https://github.com/rambod-rahmani/ffmpeg-video-player(dranger.com のチュートリアルを新しい API で書き直した動画プレイヤー)
SDL2 を使うプログラムをコンパイルするときは,リンクの指定に SDL2 を追加する。端末で,次のコマンドを実行する。
gcc -o player player.c $(pkg-config --cflags --libs libavformat libavcodec libswscale libavutil sdl2)
ffmpeg)のインストール手順はこちらの別ページで説明する。