3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

L-Smash を使って無圧縮動画を作成する

Posted at

L-Smash とは何か

L-Smash とは mp4 及び mov コンテナを読み書きできる C99 で書かれたライブラリである。名前で見て気づいた人はわかると思うが、動画編集アプリケーションのひとつ AviUtl において事実上必須となっている L-Smash Works の土台となっている。

あくまでコンテナを読み書きするライブラリなので、音声や動画のエンコード及びデコードはできないが後述の方法により L-Smash ライブラリ単体で無圧縮動画を作成することが可能である。

どうやって格納するか

L-Smash は mp4 及び mov コンテナ両方対応しているが、そのうち無圧縮動画を作成できるのは mov コンテナなので、mov コンテナを使って動画形式を RGB24 (RGB それぞれ 8bit で格納) かつ音声形式を LPCM (Linear PCM) で入れることにする。

L-Smash を使って無圧縮動画を作成する

以下 L-Smash を使って無圧縮動画を作成する方法を手順を踏む形で解説する。解説で使う言語は C++ (C++11) であるが、C++ でなければならない要素はなく、C++ 以外の言語、たとえば Swift や Kotlin Native でも原理的には応用できるとみている。

各構造体を初期化

lsmash_root_t は外向き中身の見えない構造体のため、 lsmash_create_root で初期化する。
以下の構造体は専用の初期化関数があるのでそれで初期化する

  • lsmash_movie_parameters_t
  • lsmash_track_parameters_t
  • lsmash_media_parameters_t

lsmash_file_t はあとで初期化するためここでは nullptr を指定する。

lsmash_file_t *file = nullptr;
lsmash_root_t *root = lsmash_create_root();
lsmash_movie_parameters_t movieParameters;
lsmash_track_parameters_t audioTrackParameters;
lsmash_track_parameters_t videoTrackParameters;
lsmash_media_parameters_t audioMediaParameters;
lsmash_media_parameters_t videoMediaParameters;
lsmash_initialize_movie_parameters(&movieParameters);
lsmash_initialize_track_parameters(&audioTrackParameters);
lsmash_initialize_track_parameters(&videoTrackParameters);
lsmash_initialize_media_parameters(&audioMediaParameters);
lsmash_initialize_media_parameters(&videoMediaParameters);

トラック有効化

各トラックを有効化する。動画トラックには ISOTRACK_IN_MOVIE を付加する。movieParameters.timescale は動画のフレームレートを指定する。

uint32_t audioTrackFlags = ISOTRACK_ENABLED;
uint32_t videoTrackFlags = ISOTRACK_ENABLED | ISOTRACK_IN_MOVIE;
// ここでは 60FPS とする
movieParameters.timescale = 60;
audioTrackParameters.mode = static_cast<lsmash_track_mode>(audioTrackFlags);
videoTrackParameters.mode = static_cast<lsmash_track_mode>(videoTrackFlags);

lsmash_file_parameters_t を初期化

I/O 関連及びコールバックを管理する構造体であるが、今回は特にコールバック指定は行わずファイル上でやりとりする。原理的にはコールバック指定を用いてメモリ上で読み書きすることが可能。

lsmash_file_parameters_t fileParameters;
memset(&fileParameters, 0, sizeof(fileParameters));
fileParameters.mode = LSMASH_FILE_MODE_WRITE;
fileParameters.major_brand = ISOBRAND_TYPE_QT;

lsmash_audio_summary_t を初期化

lsmash_create_summary を呼び出して各種パラメータを設定する。 lsmash_create_summary は返る値の型が lsmash_summary_t なので、lsmash_audio_summary_t にアップキャストしている。

mov コンテナかつ LPCM の場合は lsmash_qt_audio_format_specific_flags_t の設定が必要なので、 lsmash_create_codec_specific_data を使ってlsmash_qt_audio_format_specific_flags_t を設定して lsmash_add_codec_specific_data で追加する。

lsmash_qt_audio_format_specific_flags_t の設定がないとサンプル格納時にエラーになる

lsmash_audio_summary_t *audioSummary = reinterpret_cast<lsmash_audio_summary_t *>(lsmash_create_summary(LSMASH_SUMMARY_TYPE_AUDIO));
audioSummary->sample_type = QT_CODEC_TYPE_LPCAUDIO;
// ここではサンプルレート 44100Hz チャンネル数 2 サンプルビット数 16 を指定
audioSummary->frequency = 44100;
audioSummary->channels = 2;
audioSummary->sample_size = 16;
{
    lsmash_codec_specific_t *spec = lsmash_create_codec_specific_data(LSMASH_CODEC_SPECIFIC_DATA_TYPE_QT_AUDIO_FORMAT_SPECIFIC_FLAGS, LSMASH_CODEC_SPECIFIC_FORMAT_STRUCTURED);
    lsmash_qt_audio_format_specific_flags_t *format = static_cast<lsmash_qt_audio_format_specific_flags_t *>(spec->data.structured);
    uint32_t flags = QT_AUDIO_FORMAT_FLAG_SIGNED_INTEGER | QT_AUDIO_FORMAT_FLAG_PACKED;
    format->format_flags = static_cast<lsmash_qt_audio_format_specific_flag>(flags);
    lsmash_add_codec_specific_data(reinterpret_cast<lsmash_summary_t *>(audioSummary), spec);
    lsmash_destroy_codec_specific_data(spec);
}

lsmash_video_summary_t を初期化

lsmash_audio_summary_t と同じように lsmash_create_summary を呼び出して各種パラメータを設定する。

lsmash_video_summary_t *videoSummary = reinterpret_cast<lsmash_video_summary_t *>(lsmash_create_summary(LSMASH_SUMMARY_TYPE_VIDEO));
videoSummary->sample_type = QT_CODEC_TYPE_RAW_VIDEO;
videoSummary->depth = QT_VIDEO_DEPTH_COLOR_24;
// ここでは幅 960 高さ 640 で指定する
videoSummary->width = 960;
videoSummary->height = 640;

ファイルを開く

準備ができたので lsmash_open_file を使ってファイルを開く。

lsmash_create_tracklsmash_add_sample_entry をそれぞれ呼び出し、トラック及びメディアパラメータを設定する。lsmash_add_sample_entry が返すサマリインデックスはサンプル追加に必須なので保持する。

videoMediaParameters.timescalemovieParameters.timescale をそのまま割り当てる。audioSummary->samples_in_frame は音源の周波数 (= audioSummary->frequency) を動画のフレームレート (= movieParameters.timescale) で割った値を、audioMediaParameters.timescale は音源の周波数を指定する。

uint32 audioTrackID = 0, videoTrackID = 0, audioSummaryIndex = 0, videoSummaryIndex = 0;
int result = lsmash_open_file(path, 0, &fileParameters);
// 成功時は 0 を返す
if (result == 0) {
    file = lsmash_set_file(root, &fileParameters);
    // 上のコードでは既定値を指定しているためここでの分岐は無意味だが、もし音源が必要ない場合は追加させないようにする必要がある。
    // 音源がないのに音源のエントリを追加すると書き出しでエラーになるため。
    if (audioSummary->frequency > 0 && audioSummary->channels > 0 && audioSummary->sample_size > 0) {
        audioSummary->samples_in_frame = audioSummary->frequency / movieParameters.timescale;
        audioMediaParameters.timescale = audioSummary->frequency;
        audioTrackID = lsmash_create_track(root, ISOMEDIA_HANDLER_TYPE_AUDIO_TRACK);
        audioSummaryIndex = lsmash_add_sample_entry(root, audioTrackID, audioSummary);
        lsmash_set_track_parameters(root, audioTrackID, &audioTrackParameters);
        lsmash_set_media_parameters(root, audioTrackID, &audioMediaParameters);
    }
    videoMediaParameters.timescale = movieParameters.timescale;
    videoTrackID = lsmash_create_track(root, ISOMEDIA_HANDLER_TYPE_VIDEO_TRACK);
    videoSummaryIndex = lsmash_add_sample_entry(root, videoTrackID, videoSummary);
    lsmash_set_movie_parameters(root, &movieParameters);
    lsmash_set_track_parameters(root, videoTrackID, &videoTrackParameters);
    lsmash_set_media_parameters(root, videoTrackID, &videoMediaParameters);
}

オーディオフレームの追加

以下の手順を踏む形でフレームを追加する。オーディオの場合フレームは audioSummary->samples_in_frame 分のサンプルを持ったデータとみなす。

  • lsmash_create_sample を使ってサンプルを作成
  • lsmash_sample_alloc でサンプル格納に必要なデータを確保
  • memcpy でサンプルデータをコピー
  • sample->cts および sample->dtscurrent_frame_indexaudioSummary->samples_in_frame をかけた値を代入
  • sample->indexsummaryIndex を設定する
    • 設定し忘れると lsmash_append_sample でエラーになる
  • lsmash_append_sample でサンプルをコンテナに追加

外部から渡す必要のある引数は以下

  • data
    • audioSummary->samples_in_frame のサンプルを持つフレームのデータ
    • 16bit 符号ありのリトルエンディアンである必要がある
  • size
    • フレームデータのサイズ
    • audioSummary->samples_in_frame * audioSummary->channels * (audioSummary->sample_size / 8) でなければならない
  • current_frame_index
    • 現在のフレーム位置
lsmash_sample_t *sample = lsmash_create_sample(size);
lsmash_sample_alloc(sample, size);
memcpy(sample->data, data, size);
sample->cts = sample->dts = current_frame_index * audioSummary->samples_in_frame;
sample->index = audioSummaryIndex;
int result = lsmash_append_sample(root, audioTrackID, sample);
if (result != 0) {
    // エラー処理
}

ビデオフレームの追加

オーディオフレームの追加と以下の違いがあることを除いて基本的に同じ。ビデオにおいてフレームはサンプルと同一。

  • cts 及び dts には current_frame_index を直接代入する
  • index には videoSummaryIndex を入れる

外部から渡す必要のある引数は以下

  • data
    • フレームデータ
    • RGB 形式で並ぶ必要がある
  • size
    • フレームデータのサイズ
    • videoSummary->width * videoSummary->height * 3 でなければならない
  • current_frame_index
    • 現在のフレーム位置
lsmash_sample_t *sample = lsmash_create_sample(size);
lsmash_sample_alloc(sample, size);
memcpy(sample->data, data, size);
sample->cts = sample->dts = current_frame_index;
sample->index = videoSummaryIndex;
int result = lsmash_append_sample(root, videoTrackID, sample);
if (result != 0) {
    // エラー処理
}

動画の書き出し

全てのフレームを追加し終わったら lsmash_flush_pooled_samples を読んで全てのサンプルを書き出すように指示する。

lsmash_finish_movie を呼び出すとコンテナとして完全な状態で書き出される。

lsmash_flush_pooled_samples(root, videoTrackID, 0);
if (audioTrackID > 0) {
    lsmash_flush_pooled_samples(root, audioTrackID, 0);
}
int result = lsmash_finish_movie(root, NULL);
if (result != 0) {
    // エラー処理
}

終了処理

以下の関数を呼び出してクリーンアップする

  • lsmash_close_file
  • lsmash_cleanup_summary
  • lsmash_destroy_root

lsmash_cleanup_summarylsmash_summary_t にする必要があるのでダウンキャストする。

lsmash_close_file(&fileParameters);
lsmash_cleanup_summary(reinterpret_cast<lsmash_summary_t *>(audioSummary));
lsmash_cleanup_summary(reinterpret_cast<lsmash_summary_t *>(videoSummary));
lsmash_destroy_root(root);

その他

LSMASH_CODEC_SPECIFIC_DATA_TYPE_QT_AUDIO_CHANNEL_LAYOUT の設定

{
    lsmash_codec_specific_t *spec = lsmash_create_codec_specific_data(LSMASH_CODEC_SPECIFIC_DATA_TYPE_QT_AUDIO_CHANNEL_LAYOUT, LSMASH_CODEC_SPECIFIC_FORMAT_STRUCTURED);
    lsmash_qt_audio_channel_layout_t *layout = static_cast<lsmash_qt_audio_channel_layout_t *>(spec->data.structured);
    layout->channelLayoutTag = QT_CHANNEL_LAYOUT_STEREO;
    lsmash_add_codec_specific_data(reinterpret_cast<lsmash_summary_t *>(audioSummary), spec);
    lsmash_destroy_codec_specific_data(spec);
}

エンコーダ名の設定

パスカル文字列である必要があるので、最初の1byte目に長さ、2byte目以降に実体を32byte以内に収める必要がある。

// 必須ではないがエンコーダを指定する場合は指定する
const char name[] = "some encoder";
videoSummary->compressorname[0] = static_cast<uint8_t>(sizeof(name) - 1);
memcpy(videoSummary->compressorname + 1, name, sizeof(videoSummary->compressorname) - 1);
3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?