Wowza2 RTMPサーバーを使用してFlashのオーディオトラックを切り替える

この記事では、Wowza Media Server 2 RTMPサーバーを使用してFlashプレーヤー用のオーディオトラックの切り替えを実装する方法についての古代の物語について説明します。



2011年、私はAdobe Flash Playerのストリーミングサーバーの機能を調査していました。 私の仕事は、複数のオーディオトラックを持つビデオファイルを再生する方法を見つけることでした。 同時に、再生中のビデオにジャンプすることなく切り替えを行う必要がありました。 インターネットで既製のソリューションを検索しても、結果は得られませんでした。 さらに、Adobe Flash Player自体はトラックを切り替える方法を知らず、最初に遭遇したトラックのみを使用することが判明しました...



Adobe Flash Media Serverの広告は私を助けてくれました。 このサーバーの例では、アダプティブストリーミングをサポートするプレーヤーがありました。 彼は、ビデオストリームをあるビットレートから別のビットレートに、またはその逆に静かに切り替えることができました。 少し掘り下げて、次の詳細を見つけました。



私は、同じビデオでファイルが異なるオーディオトラックでこのトリックを実行しようとしました。 実験は成功し、ストリームを切り替え、さまざまなオーディオトラックを聞きましたが、あるビデオファイルから別のファイルへの移行は視覚的には見えませんでした。 しかし、Nのサウンドトラックと一緒にビデオのNのコピーも保存する必要があるため、喜ぶには時期尚早でした。これは非常に高価です。



サーバーがRTMPプロトコルを介してFlashプレーヤーに提供するデータを分析したところ、オーディオストリームとビデオストリームが互いに別々のパケットにあることがわかりました。 同時に、余分なサウンドトラックはまったく送信されませんでした。 つまり、RTMPサーバー自体が、コンテナから必要なトラックの選択に関与しています(分離)。 この情報に触発され、アダプティブストリーミングを使用したRTMPサーバーの詳細を調査し始めました。 これらのサーバーの1つはWowza Media Serverバージョン2でした。



Wowza Media Serverの特徴は、メディアファイルを再生するためのクラスを作成し、IMediaReaderインターフェイスを実装して、サーバー構成でクラスを宣言できることです。 しかし、独自のデコーダーmp4コンテナーを作成する代わりに、サーバークラスのリバースエンジニアリングを設定しました。



wms-mediareader-h264.jarファイルからMediaReaderH264およびQTMediaContainerクラスを逆コンパイルすると、次の行に気付きました。

// MediaReaderH264.class import com.wowza.wms.mediareader.h264.atom.QTAtommoov; import com.wowza.wms.mediareader.h264.atom.QTMediaContainer; . . . public class MediaReaderH264 implements IMediaReader { . . . protected QTMediaContainer container; }
      
      





 // QTMediaContainer.class public class QTMediaContainer extends QTAtom { public QTAtommoov getMoovAtom() { return moovAtom; } . . . private QTAtommoov moovAtom; }
      
      





まず、MediaReaderH264がmoovアトムにアクセスできることは明らかです。 第二に、コンテナへの参照は保護フィールドであるため、このクラスを継承してアクセスを取得できます。



moovアトムとは何ですか? mp4コンテナの仕様によると、moovアトムには、フレームレート、映画の長さ、フレームの場所、デコーダーの構成などに関するすべての情報が含まれています また、オーディオおよびビデオトラックを記述するtrakアトムのセットも含まれています。これはまさに必要なものです。



QTAtommoovクラスを逆コンパイルすると、次の図を見ることができます。

 public class QTAtommoov extends QTAtom { public QTAtomtrak getTrackByMinf(String s) { QTAtomtrak qtatomtrak = null; Iterator iterator = traks.iterator(); do { if(!iterator.hasNext()) break; QTAtomtrak qtatomtrak1 = (QTAtomtrak)iterator.next(); if(qtatomtrak1 == null || !qtatomtrak1.getMinfType().equals(s)) continue; qtatomtrak = qtatomtrak1; break; } while(true); return qtatomtrak; } public QTAtomtrak getAudioTrack() { QTAtomtrak qtatomtrak = getTrackByMinf("smhd"); try { QTAtomstbl qtatomstbl = qtatomtrak != null ? qtatomtrak.getMdiaAtom().getMinfAtom().getStblAtom() : null; if(!qtatomstbl.isValidAudioFormat()) qtatomtrak = null; } catch(Exception exception) { } return qtatomtrak; } . . . }
      
      





オーディオトラックを取得しようとすると、サーバーはすべてのtrak-atomを通過し、smhdタイプ(サウンドメディアヘッダー)を持つ最初のものを選択します。 つまり、最初のオーディオトラックが選択されます。



推測をテストするために、Wowza Media Serverライブラリコードに挿入することにしました。 最初に、逆コンパイルされたQTAtommoovクラスコードを少し修正し、それをコンパイルし直して、jarアーカイブ内のファイルを置き換えることを考えました。 しかし、驚いたことに、すべてがはるかにシンプルになりました。 サーバーアプリケーションのソースアプリケーションで、com.wowza.wms.mediareader.h264.atomパッケージを作成し、次の内容のQTAtommoov.javaファイルを配置しました。

 public class QTAtommoov extends QTAtom { public int aTrackNum = 2; . . . public QTAtomtrak getTrackByMinf(String s, int count) { QTAtomtrak qtatomtrak = null; Iterator iterator = traks.iterator(); do { if(!iterator.hasNext()) break; QTAtomtrak qtatomtrak1 = (QTAtomtrak)iterator.next(); if(qtatomtrak1 == null || !qtatomtrak1.getMinfType().equals(s)) continue; if (--count <= 0) { qtatomtrak = qtatomtrak1; break; } } while(true); return qtatomtrak; } public QTAtomtrak getTrackByMinf(String s) { return getTrackByMinf(s, 1); } public QTAtomtrak getAudioTrack() { QTAtomtrak qtatomtrak = getTrackByMinf("smhd", aTrackNum); try { QTAtomstbl qtatomstbl = qtatomtrak != null ? qtatomtrak.getMdiaAtom().getMinfAtom().getStblAtom() : null; if(!qtatomstbl.isValidAudioFormat()) qtatomtrak = null; } catch(Exception exception) { } return qtatomtrak; } }
      
      





したがって、小さな変更が行われました。最初のオーディオトラックではなく、2番目のオーディオトラックが返されました。

この形式でサーバーをコンパイルおよびデプロイすると、クラスがフックされてjarライブラリ内で動作し、Flashプレーヤーがファイル内の2番目のサウンドトラックを再生することに驚きました。 jarライブラリを再構築する必要さえありませんでした。



オーディオトラックを切り替えるプロトタイプの最終的な実装の前は、拡張クラスMediaReaderH264extを実装し、サーバー構成で宣言するだけでした。

 public class MediaReaderH264ext extends MediaReaderH264 implements IMediaReader { private String filename; private int aTrackNum; private void init(String basePath, String mediaName) { HashMap<String, String> params = new HashMap<String, String>(); String[] query = mediaName.split(":", 2); if (query.length > 1) { String[] args = query[1].split("&"); for (String arg : args) { String[] keyvalue = arg.split("=", 2); params.put(keyvalue[0], keyvalue.length > 1 ? keyvalue[1] : ""); } } filename = query[0]; aTrackNum = "rus".equals(params.get("lang")) ? 2 : 1; WMSLoggerFactory.getLogger(MediaReaderH264ext.class).info("filename: " + filename); WMSLoggerFactory.getLogger(MediaReaderH264ext.class).info("aTrackNum: " + aTrackNum); } @Override public void init(IApplicationInstance iapplicationinstance, IMediaStream imediastream, String ext, String basePath, String name) { WMSLoggerFactory.getLogger(MediaReaderH264ext.class).info("init: " + name); this.init(basePath, name); super.init(iapplicationinstance, imediastream, ext, basePath, filename); } @Override public void open(String basePath, String name) { WMSLoggerFactory.getLogger(MediaReaderH264ext.class).info("open: " + name); super.open(basePath, name); if (container != null && container.getMoovAtom() != null) container.getMoovAtom().aTrackNum = this.aTrackNum; } }
      
      





そして、サウンドを切り替えるために、Flashプレーヤーでコードが呼び出されました。

 var opt = new NetStreamPlayOptions(); opt.transition = NetStreamPlayTransitions.SWITCH; opt.streamName = "mp4e:video.mp4:lang=rus"; ns.play2(opt);
      
      






All Articles