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_track
と lsmash_add_sample_entry
をそれぞれ呼び出し、トラック及びメディアパラメータを設定する。lsmash_add_sample_entry
が返すサマリインデックスはサンプル追加に必須なので保持する。
videoMediaParameters.timescale
は movieParameters.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->dts
にcurrent_frame_index
のaudioSummary->samples_in_frame
をかけた値を代入 -
sample->index
にsummaryIndex
を設定する- 設定し忘れると
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_summary
は lsmash_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);