エイバースの中の人

アプリとWEBサービスを作っているABARSのBLOGです。

圧縮技術

OpenH264をビルドしてみる

OpenH264はCiscoが公開したH.264のソフトウェアライブラリです。BSDライセンスで自由に使えるだけでなく、DLLのバイナリをCiscoのサーバからダウンロードする形にすると、MPEG-LAへのロイヤリティを支払わなくてもよくなります。

ソースコードはGitHubで公開されています。MSVCでビルドする場合は、MinGWのMsysとnasmをインストールした上で、MakefileのOSをmsvcに書き換え、gnumakeでmakeするだけでよいです。ビルドして生成されたopenh264.libがStatic Link Library、openh264.dllとopenh264-dll.libがDynamic Link Libraryになります。

OpenH264はもともと、WebRTC向けにオープン化されたため、以下の制約があります。

・High Profileには非対応(CABACは使える)
・Bフレームには非対応

DLLのインタフェースはcodec/api/svcにあります。サンプルコードはtest/apiがわかりやすいです。画像形式はYUV411です。

機能が限定されている分、フレームを入れるとフレームが出てくるシンプルな構成で、使いやすいです。ただし、High Profileに非対応の関係で、x264でエンコードしたデータをデコードしたりするのには不向きです。現状だと、機能が限定されていても問題の少ない、エンコーダをアプリに組み込むには便利かもしれません。

最近、8x8 Transformがサポートされたように、High Profile対応は進んでいるようですので、長期的にはデコーダも広く使えるようになるかもしれません。

リリースノートはopenh264.orgで読めます。

FFMPEGよりもコードがシンプルで読みやすいので、将来性に期待しています。

FFMPEGの標準出力から動画を受けとる

FFMPEGでは、動画をデコードした結果を標準出力に出力することができます。この機能を使用すると、MP4などをデコードしたフレーム画像をアプリから利用することができます。

アプリで標準出力を受けるには、popenが使用できます。まず、FFMPEGをオプションなしで実行することで、動画の情報を取得します。以下のコードでは、C++11のregexを使用して、動画の解像度をパースしています。Windowsの場合はpopenではなく_popenを使用します。FFMPEGではファイル情報を標準エラー出力に出力するため、2>&1で標準エラーを標準出力にリダイレクトしています。

FILE *fp;
const int PIPE_BUF_SIZE=256;
char  buf[PIPE_BUF_SIZE];
std::string cmd = "ffmpeg -i sample.m4v 2>&1";
if ( (fp=popen(cmd.c_str(),"r")) ==NULL) {
  return -1;
}
std::string data;
while(fgets(buf, PIPE_BUF_SIZE, fp) != NULL) {
  data+=std::string(buf);
}
pclose(fp);

const char* pattern = " ([0-9]+)x([0-9]+)[,| ]";
regex re(pattern);
cmatch match;
if ( regex_search(data.c_str(), match, re) ) {
  if(match.size()>=3){
    m_width=atoi(match.str(1).c_str());
    m_height=atoi(match.str(2).c_str());
  }
}

const char *fps_pattern = "([0-9]*) fps";
regex re_fps(fps_pattern);
cmatch match_fps;
if ( regex_search(data.c_str(), match_fps, re_fps) ) {
  if(match_fps.size()>=2){
    m_fps=atoi(match_fps.str(1).c_str());
  }
}

次に、動画をimage2pipeで開きます。-pix_fmtにrgb32を与えることで、BGRA順のピクセルデータを取得することができます。-の指定をすることで、動画の出力先として標準出力を指定しています。フレーム数は取得できないため、freadでエラーが返ってきたタイミングが、動画の終端となります。

std::string cmd="ffmpeg -i sample.m4v -f image2pipe -pix_fmt rgb32 -vcodec rawvideo - 2>&1";
if ( (m_fp=POPEN(cmd.c_str(),"r")) ==NULL) {
  return -1;
}
while(1){
  int size=fread(image, m_width*m_height*4, 1, m_fp);
  if(size!=1){
    break;
  }
  [imageを使っていろいろ]
}
pclose(fp);

FFMPEGを使うと、マルチプラットフォームで動画の入出力を簡単に行うことができます。さらに柔軟に操作したい場合は、libavcodecを直接叩くことになります。

ロイヤリティフリーの音声CODEC Opusの分析

ロイヤリティフリーの音声CODECとして、Opusが標準化されていたので、中身を解析したいと思います。

Opusとは?


SILKとCELTを組み合わせた音声CODECです。

Opusはロイヤリティフリーのオーディオフォーマットで、VoIPや電話会議、ライブストリーミングといった「インタラクティブ」な音声や音楽を格納するために設計された。Skypeの採用する音声コーデック「SILK」と、Xiph.Orgが開発する低遅延を特徴とする音声コーデック「CELT」を組み合わせたものとなっている。

フリーのオーディオフォーマット「Opus」がIETF標準に、初の正式版実装もリリースされる


ドキュメント類


仕様書は、OpusCodecDefinition of the Opus Audio Codecにあります。リスニングテストの結果は以下のようになっているようです。全領域で優秀な結果を残しています。

opus

SILKとは?


Skypeのために開発された低遅延の音声CODECです。(Wikipedia線形予測符号化をベースとしており、周波数変換を行わず、時間領域で符号化します。

CELT(Constrained Energy Lapped Transform)とは?


OggVorbisを開発したXiph.Org Foundationが開発した低遅延CODECです。(Wikipedia)AACやVorbisと同様に、周波数変換を行いますが、窓幅を小さくすることで低遅延を実現しています。周波数変換にはMDCTを使用しており、スペクトル崩落を近似した後、係数をベクトル量子化するなど、基本的構成はVorbisと似ています。レンジコーダを使用しており、IMDCT後にポストフィルタが追加されているので、Vorbisをシンプル化してエントロピー符号化を強化したものと考えることができるかと思います。

OpusにおけるSILKとCELTの切り替え


OpusはSILKとCELTを内包しており、そのどちらか、もしくは両方を使用することができます。

ケース使用CODEC対応周波数帯域対応フレーム遅延
Low bitrateSILKNB/MB/WB10ms〜60ms
Mid bitrateHYBRIDSWB/FB10ms〜20ms
Very Low delayCELTNB/WB/SWB/FB2.5ms〜20ms


対応周波数帯域は以下の表に対応します。

Frequency4kHz6kHz8kHz12kHz20kHz
Band typeNBMBWBSWBFB


直感的には、周波数変換を行うCELTの方が遅延が大きそうですが、SILKの方が遅延が大きいのが面白いです。

これより、Opusにおいて、ビットレートが低い場合はSILK、ビットレートが高い場合はSILK+CELT、低遅延が要求される場合はCELTで符号化されるようです。

Hybrid modeとは?


では、SILK+CELTではどのように符号化されるのでしょうか?この場合、8kHz以下の周波数成分はSILK、8kHz以上の周波数成分はCELTで符号化されます。デコーダでは、8kHz以下の周波数成分を単純に0で埋めてIMDCTを行い、SILKの出力に加算することで、音声波形を得ることができます。

他のCODECのアーキテクチャとの比較


低周波成分と高周波成分の符号化方法の観点から、一般的なCODECのアーキテクチャとの比較を表にまとめてみました。

CODEC〜8kHz8kHz〜
MP3/AAC/VorbisMDCTMDCT
HE-AACMDCTSBR(*)
Opus Low bitrateSILK-
Opus Mid bitrateSILKMDCT
Opus Very low delayMDCTMDCT

(*)SBRの開始周波数は8kHzとは限りません

OpusのVeryLowDelayは、特に特筆するところのない普通の構成です。OpusのLowBitrateも、VoIP向けの普通の構成だと思います。

Opusの斬新さは、MidBirateのHybridモードです。Opusでは、低周波成分をSILKの線形予測符号化で、高周波成分をMDCTで作っています。線形予測符号化は、ボイスなど、モデルにうまく当たるものに有効なので、〜8kHzの低周波領域にSILKを使い、それ以外の予測の当たりにくい領域でMDCTを使用します。

まとめ


VoIPに最適なSILKと、一般的な音声に最適なCELT、そして斬新なHybridモードを、自由にエンコーダで選択できるCODEC、それがOpusです。エンコーダで遅延をコントロールでき、ボイスに限らず一般的な楽曲にも適用できるため、ライブ配信や、ワイヤレスオーディオ、ゲームのリモートプレイ、ボイスチャットなど、広く普及していく可能性があります。

Blu-rayディスクなどは、相対的に画像の情報量が多いため、音声はリニアPCMなど、圧縮率よりも音質重視の方向に向かっています。そのため、圧縮率が要求される通信や、現在Vorbisが担っているゲーミング分野などが、メインの市場となっていくのかなと思います。

リファレンス


Opus Codec
Opus 正式リリース - 蒼弓ノート

HEVC(H.265)を使ってみた

H.265という名前がITUに承認されたということで、HEVCのエンコーダとデコーダを使ってみました。リポジトリはFraunhoferのHigh Efficiency Video Coding (HEVC)にあります。HEVCのエンコーダとデコーダの使用方法は、ReferenceSoftwareManualに記載されています。

HEVCのビルドと実行


まず、SubversionをApacheSubversionからインストールします。一番下のwin32svnを使用しました。インストール後、C:\Program Files (x86)\Subversion\binにパスを通します。

(1)リポジトリをチェックアウト

svn co svn://hevc.kw.bbc.co.uk/svn/jctvc-hm/


(2)ライブラリとエンコーダ・デコーダをビルド

VisualStudio2010で以下のプロジェクトをビルド。
jctvc-hm\trunk\build\HM_vc10.sln


(3)ビルドしたバイナリを取得

以下のフォルダにTAppEncoder.exeとTAppDecoder.exeがあります。

jctvc-hm\trunk\bin\vc10\Win32\Release


(4)YUVファイルからエンコード

画像サイズとフレームレートとフレーム数、画質を設定します。エンコーダの入出力はYUVファイルなので、ImageMagicKを使って変換しています。

convert src.bmp src.yuv
TAppEncoder.exe -c encoder_intra_main.cfg -i src.yuv -b str.bin -wdt 320 -hgt 160 -fr 1 -f 1 -q 1
TAppDecoder.exe -b str.bin -o out.yuv
convert -size 320x160 out.yuv out.bmp


使ってみた印象


手元で動かしてみた印象としては、イントラの高圧縮領域で2[dB]近く性能が向上していて素晴らしいです。HEVCはスーパーハイビジョンを想定しているということもあり、使ってみるまでは、画像の解像度が大きくないと、H.264とあまり差が出ないかなと考えていたのですが、QVGAの画像に対してもH.264のJMとの差が1[dB]近く出ていて驚きました。HEVCはかなり優秀なCODECだと思います。

アニメ系でもほとんどモスキートノイズが出ないので、アニメは全部HEVCになると幸せになれると思います。14年7月に開始という噂のNHKの4k放送もHEVCになってほしいですね。

演算負荷と考察


デコーダは、マクロブロックライン単位のスレッド並列デコードが可能になったのと、デブロッキングフィルタの演算負荷が削減された関係で、H.264と同等か、むしろ高速にデコードできるようです。Docomoは、HEVC復号ソフトウェアにおいて、以下のような驚異的なデコード性能を出しています。

本復号ソフトウェアにて、スマートフォン上でHEVCのフルHD動画、汎用パソコン上で秒60コマの4K動画の再生が実証されています。


課題はエンコーダで、HEVC Complexity and Implementation Analysisによると、現状はフルHDの動画のイントラオンリーのエンコードで、実時間の1000倍のエンコード時間が必要となっています。

Times are recorded in tens of seconds such as to illustrate the ratio to real-time operation.Even for intra-only configurations, the encoding time may exceed 1000 times real-time. Encoding times were obtained
on a cluster containing Xeon-based servers (E5670 clocked at 2.93 GHz) and using gcc 4.4.5.


スマートフォンでは、チップの高性能化がすごいスピードで進んでいるので、数年でハードウェアデコーダの搭載が当たり前になってくるかと思います。そのタイミングで、エンコーダの高速化がどこまで進んでいるかが重要そうです。予想としては、最初のエンコーダは、圧縮性能を落としてエンコードを高速化したものが市場に出てきて、それから時間を経て、リファレンスモデルの性能に近づいていくのかなと思います。

オススメする文献


HEVCの仕様については、以下のPDFにまとまっています。

Overview of the High Efficiency Video Coding(HEVC) Standard


日本語書籍としては、インプレスのHEVC教科書が一番オススメです。HMの使い方と、HMのλパラメータの解説があります。



また、”高効率映像符号化技術 HEVC/H.265とその応用 ”もコンパクトにデコーダ仕様がまとまっています。



P.188のAVC/H.264とHEVC/H.265の相違点を引用します。

イントラ符号化に関するAVC/H.264とHEVC/H.265の相違点は
・イントラ予測モードの多様化
・参照画素、および参照画素と予測画像の間の適応平滑化フィルタ
・予測・変換ブロックサイズの多様化
である。

インター符号化に関するAVC/H.264とHEVC/H.265の相違点は
・マージモードの導入
・適応動きベクトル予測
・小数画素生成用の内挿フィルタの改善
・予測・変換ブロックサイズの多様化
である。

WebPの解説:動画像圧縮から静止画像圧縮へ

WebPがTechcrunchに取り上げられて盛り上がっているようなので、WebPとは何かについて書いてみることにします。元記事はこちら:Googleの画像フォーマットWebPはJPEGよりも軽くて鮮明

そもそも、今、一番使われている静止画像圧縮であるJPEGというのは、1992年に標準化された技術です。この進歩の早いコンピュータ業界で20年近く前の技術がまだ現役で使われているわけですね。なぜ最新の技術に置き換わっていかなかったかというと、

(1)PCのネット回線の帯域が拡大して静止画像はあまり圧縮できなくても困らなくなった
(2)高画質が要求されるカメラなどのプロフェッショナル用途はRAW画像など無圧縮に行ってしまった
(3)JPEG2000が普及しなかったことを受けて積極的に新技術を導入していこうという流れが無くなってしまった

という三点が大きいですね。そんな感じで最近は、静止画像圧縮単独での研究というのはあまりなされなくなりました。

対して、動画の世界では、TVのフルHD化などに見られるコンテンツの高解像度化に伴い、ディスクメディアや放送回線の容量が足りなくなってきたため、より高圧縮の技術が求められ続けてきました。動画は、過去にデコードした画像を参照することで圧縮率を高めていますが、初期状態では参照できる画像が無いのと、シークができないといけないということで、デコードした画像を参照しないモードもあります。専門的には、過去の画像を参照しないフレームをIピクチャ、過去にデコードした画像を参照するフレームをPピクチャと呼びます。そして、動画像圧縮の世界では、JPEG相当の技術であるMPEG1から、MPEG2、H.264という流れで、IピクチャおよびPピクチャの圧縮技術は順調に進化してきました。そうです、このIピクチャというのは過去のフレームを全く参照しない、つまり静止画像圧縮と全く同じなんですね。

そしてここに来て、モバイル端末の普及によって低速な回線を使わざるを得ない環境ができ、その上、スマートフォンの登場によって画面の高解像度化が進み、静止画像であってもできるだけ高い圧縮率が求められるようになってきました。

そして、今、起こっているのは、動画像圧縮を静止画像圧縮として使おうという流れです。具体的に、WebPは、WebMのIピクチャそのものですし、ACCESSはH.264のIピクチャをミドルウェア化してビジネスをしようとしているわけです。JPEGから比べればH.264のIピクチャは2倍〜3倍の圧縮率を持ちます。これを使うことで一気に圧縮率が改善するわけです。

具体的に、JPEGと、WebPやH.264がどう違うのかというと、まず、JPEGは8x8ピクセルブロック単位で独立してDCTを行います。つまり、大きな画像があったとしても、圧縮は8x8ブロック単位で独立してやってしまって、8x8ブロック間の相関は全く使わないわけです。こうすると、どうしてもブロック間にブロックノイズが出ます。そこで、WebPやH.264では、イントラ予測を使ってこの問題を回避しています。つまり、8x8ブロックをそのままDCTするのではなくて、周辺の画像から予測画像を作って、元画像と予測画像との差分をDCTすることで、圧縮率を向上させています。例えば、JPEGで超高圧縮にして、特定のブロックの情報量を0にした場合、そのブロックの画素値は真っ黒になりますが、WebPやH.264では差分値が0となっても予測画像は残るので、JPEGのように全く絵として成立していない状態にはなりません。また、エントロピー符号化においても、JPEGのような単純な二次元ハフマンではなく、従来のデコードシンボルに応じて動的にテーブルを切り替えていくコンテキストベースのバイナリ算術符号化になっているなど、総合的に圧縮率が向上しています。

そういう意味では、WebPは新に画期的なものではなく、動画像圧縮で培われたIピクチャの技術を、静止画像圧縮のフォーマットとして転用したものなんですね。

ちなみに、イントラ予測では左と上と右上の処理済みブロックを参照しますが、JPEG2000になると、階層化によって右と下も予測に使えます。これによってより綺麗に予測できるのですが、その分、使用メモリ量が増大するのと、Pピクチャにおけるブロック単位の動き補償との相性が悪いせいで、動画像圧縮の世界ではウェーブレット変換はあまりメジャーにはなっていません。

WebPは、もし、Androidにネイティブ搭載されて、iOS用のライブラリとかも提供されるようになれば、それなりに普及しそうな気がしています。ただ、iOSのSafariには採用されなそうな気がしていて、そうすると検索に誘導するというGoogleのビジネス的にはメリットが薄くなるため、Googleがどれぐらい投資し続けるかという判断は難しそうな気はしています。また、やはりDCTベースなのでアニメ画像とかとの相性はそこまで高くないというのも注意しておく必要がありますね。何にしろ、ついに静止画像圧縮もJPEGから次のステップに入っていくのかなと思うと楽しいです。

WebM(VP8)の性能

使ったサンプルが特殊でグラフは出せなくて申し訳ないのですが、WebM(VP8)のPSNRを計測したので雑感だけ書きます。計測手順は以下です。

まず、WebMのFTPサイトからvpx-vp8-debug-src-x86-win32mt-vs8-v0.9.0.zip libvpx 0.9.0 visual studio buildをダウンロードしてきて、コマンドラインエンコーダ&デコーダであるivfenc.exeとivfdec.exeを入手します。これらの入出力フォーマットはJMと同じRAW YUV形式です。計測に使ったサンプルは連番BMPなので、これをまず、imagemagickでyuv形式に変換します。例えばconvert 0000.bmp 0000.yuv。これをフレーム数分だけ実行して、出来上がったyuvファイルを全て単純に結合します。こうしてできたin.yuvをivfencに入力します。

コマンドラインオプションとしては、画像サイズと、クオリティを入力する必要があります。クオリティには--target-bitrateが使えますが、ビットレート制御を行うとPSNRが落ちて不公平な評価になってしまうので、--target-bitrate=1000000000と大きくしておいて、--max-q=10 --min-q=10のように固定qになるように制約します。qの範囲は[0,63]ですね。こうすると、ビットレート制御ではなくクオリティ制御になりますので、VBRになって性能が高くなります。

ivfenc --width=320 --height=240 --target-bitrate=1000000000 --max-q=10 --min-q=10 in.yuv out.webm

ということで、qを変えながらグラフを取っていくことになります。デコードは

ivfdec out.webm out.yuv

としてデコードした後、

convert -width 320 -height 240 out.yuv out.bmp

でフレーム単位で切り出して連番BMPに落とし、元の画像とのPSNRを計測します。

比較対象は、x.264とJM。x.264はHighProfile+ExhibitSearch+CABAC、JMもできるだけ最大オプションでエンコードしています。入力素材はVGAとQVGAサンプルです。

ということで結果なのですが、圧縮性能はほぼx264と同じか少し下ぐらいでした。エンコード速度はx264よりもそれなりに遅いかなという感じです。エンコードがとてつもなく遅い代わりに圧縮率が最高になるH.264のリファレンスモデル、JMに比べると少し低く出る感じですね。WebMがH.264から大分簡略化されていることを考えると、もっと低く出るかと思っていたんですが、想像以上によい結果でした。

ちなみにエントロピー符号化はバイナリ算術を使っているようです。昨日の記事で、DCT4x8を使っていると書いてしまいましたが、コードを見てみるとDCT4x8の関数の中でDCT4x4を二回呼び出していたので、そこは別に新しくは無かったようです。本当に、H.264の使える所だけを残した感じですね。高画質領域ではブロックサイズが小さくても問題なくて、高圧縮領域では重めのDCTやループフィルタでがんばっていると。

ということで、十分にH.264のHighProfileと競合するCODECな予感がします。Appleさんとしてもこれだけオープンであれば特に拒否する理由も無い気がするので、パテント問題さえ解決すれば一気に普及しそうですね。家電はH.264、インターネット&モバイルはWebMというのがありえそうな未来図です。ただ、ほとんど264なのでパテント問題は重そうです。

ちなみに、x264の中の人のまとめが早くて詳しすぎます。必読。description of VP8 would be “H.264 Baseline Profile with a better entropy coder”. というのは言い得て妙だな
と思いました。

GoogleがVP8をWebMとしてオープン化したのでコードを読んでみた

GoogleがVP8をWebMとしてオープン化しました。本日よりソースコードがダウンロードできます。

第一印象は、H.264から効果の少ないモードやプロファイルを取り去ってシンプル化した上に、新しいツールを組み込んだCODECかなという所です。基本はH.264などと同じ、DCTと1/4画素予測の動き補償を併用したハイブリッド符号化です。

ブロック符号化の要であるブロックサイズについては、dct.hで

typedef prototype_fdct(*vp8_fdct_fn_t);
typedef struct
{
vp8_fdct_fn_t short4x4;
vp8_fdct_fn_t short8x4;
vp8_fdct_fn_t fast4x4;
vp8_fdct_fn_t fast8x4;
vp8_fdct_fn_t walsh_short4x4;
} vp8_fdct_rtcd_vtable_t;

と定義されていて、最大DCTサイズが4x4と、16x16の264に比べて極めて小さいのが独特ですね。フルHDだと264の方が性能が高くなりそうです。dct8x4は最初、インターマクロブロックタイプに応じて適応的にDCTの形状を変えているのかと思っていたんですが、実際はdct4x4を二回呼び出しているだけでした。

動き探索は

extern int vp8_hex_search
(
MACROBLOCK *x,
BLOCK *b,
BLOCKD *d,
MV *ref_mv,
MV *best_mv,
int search_param,
int error_per_bit,
int *num00,
vp8_variance_fn_t vf,
vp8_sad_fn_t sf,
int *mvsadcost[2],
int *mvcost[2]

);

typedef prototype_full_search_sad(*vp8_full_search_fn_t);
extern prototype_full_search_sad(vp8_full_search_sad);
extern prototype_full_search_sad(vp8_full_search_sadx3);

typedef prototype_diamond_search_sad(*vp8_diamond_search_fn_t);
extern prototype_diamond_search_sad(vp8_diamond_search_sad);
extern prototype_diamond_search_sad(vp8_diamond_search_sadx4);

と、ヘキサゴンサーチとダイヤモンドサーチを併用していそうな予感をさせています。

また、動きベクトル精度に関しては、
 int vp8_find_best_sub_pixel_step_iteratively(MACROBLOCK *x, BLOCK *b, BLOCKD *d, MV *bestmv,  MV *ref_mv, int error_per_bit, vp8_subpixvariance_fn_t svf, vp8_variance_fn_t vf, int *mvcost[2])
にて
// 1/2 pel
// 1/4 pel
というコメントがあるので1/4pelですね。

イントラ予測はblockd.hにて、264から少し数を減らしたようなモードが定義されています。

typedef enum
{
B_DC_PRED, // average of above and left pixels
B_TM_PRED,

B_VE_PRED, // vertical prediction
B_HE_PRED, // horizontal prediction

B_LD_PRED,
B_RD_PRED,

B_VR_PRED,
B_VL_PRED,
B_HD_PRED,
B_HU_PRED,

LEFT4X4,
ABOVE4X4,
ZERO4X4,
NEW4X4,

B_MODE_COUNT
} B_PREDICTION_MODE;

VP7,8のお家芸、ループフィルタについてはloopfilter.cにて。

// Horizontal MB filtering
// Vertical MB Filtering

これも264に近く、水平・垂直方向にフィルタをかけているようです。

if (mb_col > 0)
cm->lf_mbv(y_ptr, 0, 0, post->y_stride, 0, &lfi[filter_level], 0);

if (mbd->mode_info_context->mbmi.dc_diff > 0)
cm->lf_bv(y_ptr, 0, 0, post->y_stride, 0, &lfi[filter_level], 0);

// don't apply across umv border
if (mb_row > 0)
cm->lf_mbh(y_ptr, 0, 0, post->y_stride, 0, &lfi[filter_level], 0);

if (mbd->mode_info_context->mbmi.dc_diff > 0)
cm->lf_bh(y_ptr, 0, 0, post->y_stride, 0, &lfi[filter_level], 0);

マクロブロック単位でこんな感じ。vp8_mbloop_filter_horizontal_edge_cという記述があるので何かしらエッジ適応をしていそうです。また、#define MAX_LOOP_FILTER 63という数字が少し気になります。大量のフィルタを用意しておいてブロックに応じて変えている?

後はインターマクロブロックモードがどのくらいあるか気になりますねー。それとPSNRを計ってみたい。

ということでソースコードはJMに比べて非常に読みやすいです。なおかつ、非常にコンパクトなので、モバイル向けにも結構いい性能が出そうな予感がしています。ただ、ベースはほぼ264で、有効なツールだけを残してかなり整理した印象があるので、エンコーダ最適化の伸び白という意味では264の方が大きいかもしれません。264は何でも入っていて、それぞれエンコーダでON/OFFできるので、思ってもいなかった組み合わせで性能が上がったりもするのが面白いですね。

AdobeのFLASHでデフォルトサポートされたり、FirefoxとChromeで今日から使えるというのも本気を感じますね。264陣営とWebM陣営の面白い戦いが見れそうです。ただ、本当にパテントフリーかは注意する必要があるかもです。OggVorbisのようにMP3やAACから、MDCTという点を除けばかなり特殊なことをやっていたのと違い、こちらは大分264に近い印象があるので、そのあたりで各社がどう出るかも楽しみですね。それと、OggVorbisのデファクト化は、2002年のver1.0から考えると実に8年越しで、とても感慨深いです。OggVorbisは独創的でいいCODECなので、これを機会に普及するといいですね。

ということで速報でした。30分ぐらいしかコード読んでいないので間違っている可能性が多々あることにご注意下さい。さて会社行かねば。

MDCTは可逆です

「MDCT 2048」で検索するとMDCT(Modified-DCT)は可逆ですか?というのが出てきます。正解は可逆なのですが、正解は不可逆のように誤解されていて、さらにそれが検索上位に出てきてしまっているので、僻地ながらここで訂正をしようと思います。

質問は”MDCT-IMDCTをすると音が戻らない。MDCTは可逆変換か?”というもので、回答は”低周波域で計測される周波数の間隔が2倍に粗くなってしまう。だから低周波域の情報が欠落していてもおかしくない。”というものです。その回答に対して、質問者は”MDCTはDCTに比べて周波数領域での観測できるスペクトルが2倍間隔になるから、特に低音で音がなくなっている。”と理解されました。

ですが、MDCTは可逆です。確かに、窓幅2048に対して周波数成分の数は1024ですが、半分ずつオーバラップして周波数変換を行うので、周波数成分の合計数は時間波形成分の合計数と一致します。次元は退化していませんので、ちゃんと元の波形に戻ります。

確かに、1窓で比べると、MDCTとDFTの周波数成分の間隔は2倍違いますが、直交性は残っています。それに、 ”低周波域で計測される周波数の間隔が2倍に粗くなってしまう。”というわけではなく、全周波数が均等に荒くなります。

質問者の実装で音が戻らないのは、窓関数をかけていないためだと思います。DFTでは音が戻ると書かれていますが、窓関数をかけてからDFTを行うと、当然IDFTした結果も窓関数のかかった値となって元には戻らないので、質問者は窓関数をかけていないのでしょう。しかしMDCTは、その直交性の前提としてPrincen-Bradley条件を満たす窓関数を要求します。ですので、ちゃんとSin窓やVorbis窓や修正KBD窓などをかけてからMDCTを行い、IMDCTの結果に窓関数をかけてやらないと元の音には戻りません。

また、”AAC等のコーデックでは低音のことをかんがえて十分な窓幅をとっている。”わけではなく、単にまとめて処理する単位を大きくした方が圧縮効率がいいからです。MDCTは窓幅8でも直交性が存在します。

AACの仕組み

AACはMPEG-2 AdvancedAudioCodingの略です。MP3がMPEG-1 Layer3だったように、動画圧縮CODEC中の音声CODECという扱いになります。実は上位互換でMPEG-2 Audioが開発されていたんですが、それじゃぜんぜん圧縮率が出ない!ということで互換性を廃して開発されたものになります。iPodに採用されて爆発的に普及しましたね。音質はMP3の比ではなく、同じ音質で約1.5倍の圧縮が可能になります。

では、どこからその圧縮率の違いが来るのか。

一番大きいのは窓幅です。AACはMP3の約2倍の窓幅を持っています。窓幅とは周波数変換の処理単位のことです。処理単位が多ければ多いほど、まとめて削っちゃったり、より細かく解析できたりするので、圧縮の世界においては窓幅の効果は支配的です。従ってAACのアドバンテージのほとんどはここにあります。さらに、MP3では演算量を削減するためにサブバンド分割してから周波数変換を行っていましたが、AACではダイレクトに2048点MDCTで周波数変換してしまいます。シンプルで素敵ですね。

ただ、窓幅を大きくすると大きくするなりの問題もあります。特に大きいのがプリエコーで、周波数成分一つの誤差は窓全体に分散するので、ハイハットとかの突発的、非定常的な音源で、音の直前にノイズが乗ってしまいます。そこで使われるのが窓幅の適応的変更です。非定常的な場所では窓幅を256にしてしまうのです。256と2048のつなぎ目は、えらい人が考えたえらいかっこいい窓関数があるので、完全再構成が可能になります。

後はいつもどおり周波数成分に聴覚心理モデルをかけて、どの帯域にどれぐらいの容量を割り振るかを決めて、エントロピー符号化するだけです。そういえばMSステレオもMP3ではフレーム単位でしたが、AACではバンド単位で変更が可能になりました。

基本はこれだけなんですけど、いくつか強力なツールも搭載されています。まず、周波数成分のフレーム間予測。動画でも動き予測で一つ前の絵を使って今の絵を予測したりしますが、それの音声版です。周波数成分単位で予測値を、前2フレーム分の対応周波数成分から生成し、実際の周波数成分との差分を符号化します。予測の使用/不使用は、バンド単位で可能です。また、ロバストにするために定期的に予測のリセットを行いますが、全てのバンドを同時にリセットするとそのフレームの符号量が増大してしまうので、今回はバンド0のリセット、次回はバンド1のリセット、みたいに、ずらしながらリセットします。

ただし即座に分かるように、周波数成分をバッファリングする必要があったり、シークどうするよ、とか問題山済みなので、Main-profileでのみ採用となっています。iPodのAACはLow-profileなので使ってなかったりします。

もう一つはTemporalNoiseShapingで、周波数変換の直後、フレーム内の周波数成分に対して予測を行い、周波数成分を残差に変換して符号化しますが、これが何で時間領域での雑音整形になるのかはいまいち理解できていません。きっとすごい方法なのでしょう。

さらに詳しく知りたい方は

をどうぞ。

そういえば

を買いました。レビューはそのうち…っと思っていましたが、先に挙げた方で十分です。MDCTのSPLの計算式も無いですし、MPEG-1の聴覚心理モデルの解説も、MP2で使われるものまでしか書かれていません。やっぱりIntroduction to digital audio coding and standardは名著ですねー。

ちなみに着うたで使われているHE-AACは、AACにSBRという高周波の予測を加えたものです。人間の耳は、高周波成分をパワーでしか認識していなくて、そのおかげでインテンシティステレオというL+RとL,Rのパワー比だけを保持するという手法が使えるんですが、さらにそれすら保持せずに、低周波から高周波を予測して勝手に作っちゃうというアバンギャルドな手法です。ただしこれが効果があるのはインテンシティステレオすら使えない超高圧縮域だけですので、実はいらない娘だったりします。HE-AACではVorbisっぽい音になります。

MPEG-2からMPEG-4へはあんまり進化しなかったので、そろそろ音声系はさちってきた気もします。逆にそれがチャンスなのかもしれません。

MP3の仕組み

MP3は数ある音響信号圧縮CODECの一つです。インターネットの普及と合わせて広く使われるようになって、一気に普及しました。

一般に音は、データの偏りが少ないためあまり圧縮できません。従来は、時間領域での相関を利用し、前後の音の差分を適応的に符号化するADPCMが使われていましたが、CD音質で1/4程度までしか圧縮できませんでした。そこで、MP3では、周波数領域で高周波成分や耳の鈍感な部分に割り当てる情報量を減らす事によって、1/10程度までの圧縮を実現します。耳はそもそも周波数領域で音を認識するため、周波数領域で処理する事によって、より実認識に近い形で処理できます。

MP3では、音を任意のブロック単位で処理します。これは、周波数変換がOrder(N LogN)の計算量なので、演算量的に曲全体を一度に周波数変換する、という事ができないためです。ここで、周波数変換はブロック単位で窓関数をかけて行います。これは、周波数成分の分析能力をあげるためです。しかしここで、窓関数をかけた場合、ブロックの両端が0に落ちてしまい、元の音声が復元できなくなってしまいます。そこで、ブロックを半分ずつ重複させる重複変換が行われます。しかし、その場合、重複分、周波数変換後のデータが多くなってしまい、データ量が二倍となります。それを解決するために、半分の周波数成分で完全再構成が保障されるMDCT(Modified Descrete Cosine Transform)を使います。MDCTを使う場合、窓関数には多少の制約がつきます。MDCTの実装においては、ネイピア数の部分を式変形する事でFFTの形にし、FFTで処理でき、OrderはNlogNにできます。また、DCTの形に変換する事で、最小のバッファサイズで実装する手法も提案されています。

MP3の場合は、先にフィルターバンクで帯域分割してからMDCTを行います。これは、MDCTの演算量を極力減らすための工夫です。周波数変換されたデータはクリティカルバンドごとに分割されて処理されます。クリティカルバンドとは、人間の耳の認識周波数単位であるBarkスケールの単位で、25個あります。低周波ほど狭く、高周波ほど広くなっています。これは、人間の耳が低周波ほど敏感である事を示しています。バンド分割されたデータは、バンドごとに特定のビット数が割り当てられ、そのビット数で量子化、ハフマン符号化され格納されます。ここで、どのバンドにどれだけのビット数を割り当てるかは、聴覚心理モデルにより求められるマスキング値との差によって決定されます。これにより、耳の鈍感な部分には少ないビットが、敏感な部分には多くのビットが割り当てられます。

聴覚心理モデルでは、FFTが出力する周波数成分を用います。これは、聴覚心理モデルを適用するには複素成分が必要なのですが、MDCTは複素成分を持たないため、FFTを補助的に用いる必要があるためです。ちなみに最近は、MDSTを複素成分とするCMDCTが提案されています。聴覚心理モデルにおいては、絶対的に小さな音を認識できない(ATH)、大きな音の近くの小さな音は認識できない(同時マスキング)、大きな音の後に鳴る小さな音は認識できない(時間マスキング)というデータに基づき、これ以下の音は聞こえないというマスキング値が決定されます。ATHは周波数に対応して一意に定まります。最初に実験データであるLoudness曲線があり、その後解析的に定式化されました。同時マスキングは、全周波数成分に対してSpreadingFunctionと呼ばれる三角形領域を構築し、その値のmaxを取る事で計算できます。時間マスキングは、以前のマスキング値を減衰させていき、現在のマスキング値とのmaxを取る事で計算できます。

一般に、MP3は聴覚心理モデルによって音をカットするため音が悪くなる、といわれますが、ATH以外の部分では0に落としていないため、128kBps程度では可聴域の部分でのマスキングは行われていません。適応的に割り当てビット数を割り当てるために用いられているだけです。適応的に割り当てなければ耳の敏感な部分からよりデータが削られるわけで、聴覚心理モデルによって音が悪くなっているわけではありません。よく音を周波数アナライザで見て、高周波が出ている!だからいい!という人がいますが、その聞こえずらい高周波部分の情報量だけ、よく聞こえる低周波部分から情報は削られているのです。音は目ではなく耳で聞くものですので、注意しましょう。

こうして求められたマスキング値とMDCT周波数成分値との差のlog2スケールで割り当てビット数を決定します。ここで、要求ビットレートにおさまるように、ビット数の総和を調整します。1bitずつ一番差が大きいバンドに与えていくものや、エントロピーを求めて解析的に計算するもの等があります。この辺りはよく特許がとられています。

量子化・符号化されたバンドは順次ストリームに出力されていきます。

MP3の窓幅は1152と小さいため、低ビットレートでは根本的に最低限必要な音分の情報量が確保できず、悲惨な音になります。

次回はAAC?
Search
Profile

abars

アプリとWEBサービスを開発しています。最近はUnityとGAE/pyが主戦場。

ブラウザ向けMMOのメトセライズデストラクタ、イラストSNSのイラストブック、東証の適時開示情報を検索できるTDnetSearchを開発しています。

かつてエンターブレインのTECH Win誌でATULADOを連載しました。

サイト:ABARS
Twitter:abars
Github:abars

Twitter
TopHatenar
HotEntry
Counter

アクセス解析付きカウンター。あなたのBLOGにもどうですか?登録はこちらから。

TOP/ BLOG/ LECTURE/ ONLINE/ RUINA/ ADDON/ THREAD/ METHUSELAYZE/ IPHONE/ MET_IPHONE/ ENGLISH/ RANKING