このトピックでは、ネイティブRTS SDKをFFmpegに基づくサードパーティのプレーヤーに統合して、iOSクライアントにリアルタイムストリーミング (RTS) を実装する方法について説明します。 このトピックでは、サードパーティのプレーヤーijkplayer k0.8.8が使用されます。
前提条件
ijkplayerのソースコードはコンパイルされています。 詳細については、ijkplayerのREADME.mdファイルをご参照ください。
手順
ijkplayerのソースコードをダウンロードして解凍します。 ダウンロードリンクを取得するには、ijkplayerページにアクセスしてください。
ネイティブRTS SDKパッケージをダウンロードして解凍します。 ダウンロードリンクの詳細については、「SDKダウンロード」をご参照ください。
ネイティブRTS SDKをプラグインとしてijkplayerに統合します。 次の表に、2つの統合方法を示します。
統合方法
説明
利点
デメリット
FFmpegを拡張します。
デマルチプレクサプラグインを追加してFFmpegを拡張します。
この方法は開発を簡単にする。 Alibaba Real-Time Communication (ARTC) ベースのURL用に追加のロジックを開発する必要はありません。
FFmpegを再コンパイルする必要があります。
ijkplayerを拡張します。
AVInputFormat構造体を追加してijkplayerを拡張します。
FFmpegをコンパイルする必要はありません。
ff_ffplay.cファイルにロジックコードを追加する必要があります。
IJKMediaPlayerプロジェクトをコンパイルします。
FFmpegまたはijkplayerを拡張した後、アーカイブコンパイラを使用してijkplayer/ios/IJKMediaPlayer/IJKMediaPlayer.xcodeprojプロジェクトを実行します。 IJKMediaFramework.frameworkファイルは、Productsディレクトリに生成されます。
オプション: HTTPSプロトコルでストリームをサポートする場合は、コンパイルを実行する前に、XcodeでプロジェクトのBuild Phases > Link Binary With Librariesを選択し、前の手順で生成されたlibssl.aおよびlibcrypto.aファイルをijkplayer/ios/build/universal/libディレクトリにインポートします。

Native RTS SDKのRtsSDK.frameworkファイルとijkplayerのIJKMediaFramework.frameworkファイルをプロジェクトに挿入します。

ijkplayerによって提供されるメソッドを呼び出して、RTS機能を使用します。
ijkplayerの作成
// Generate a URL over the ARTC protocol. _url = @"artc://xxxx"; // Create a custom playerView. [self.view addSubview:self.playerView]; ... IJKFFOptions *options = [IJKFFOptions optionsByDefault]; // Configure hardware decoding. [options setPlayerOptionIntValue:1 forKey:@"videotoolbox"]; // Configure software decoding. //[options setPlayerOptionIntValue:0 forKey:@"videotoolbox"]; _ijkPlayer = [[IJKFFMoviePlayerController alloc] initWithContentURL:[NSURL URLWithString:_url] withOptions:options]; _ijkPlayer.view.autoresizingMask = UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight; _ijkPlayer.view.frame = self.playerView.bounds; _ijkPlayer.scalingMode = IJKMPMovieScalingModeAspectFit; // Specify the scaling mode. _ijkPlayer.shouldAutoplay = YES; // Enable autoplay. [self.playerView addSubview:_ijkPlayer.view];セット再生コントロール
再生を開始します。
メインスレッドでメソッドを呼び出す必要があります。 毎回再生を再開する前に、再生を停止してプレーヤーをリリースすることをお勧めします。
dispatch_async(dispatch_get_main_queue(), ^{ [_ijkPlayer prepareToPlay]; [_ijkPlayer play]; });再生を停止し、プレーヤーを解放します。
// Stop playback. [_ijkPlayer stop]; // Release the player. [_ijkPlayer shutdown]; _ijkPlayer = nil;
RTSイベントを聴く
ijkplayerでNative RTS SDKのメッセージコールバックを聞く
ijkplayer/ios/IJKMediaPlayer/IJKMediaPlayer.xcodeprojプロジェクトを開き、適合したメソッドを関連ファイルに追加します。
static int audio_open(FFPlayer * opaque、int64_t wanted_channel_layout、int wanted_nb_channels、int wanted_sample_rate、struct AudioParams * audio_hw_params){}メソッドの後に次のコードブロックを挿入して、ff_ff_ffplay.cファイルを変更します。extern int artcDemuxerMessage(struct AVFormatContext *s, int type, void *data, size_t data_size); //aliyun rts:receive artc message int onArtcDemuxerMessage(struct AVFormatContext *s, int type, void *data, size_t data_size) { return artcDemuxerMessage(s, type, data, data_size); } int artcDemuxerMessage(struct AVFormatContext *s, int type, void *data, size_t data_size) { //aliyun rts:send message to app FFPlayer *ffp = (FFPlayer *)s->opaque; const char *data_msg = (const char *)data; ffp_notify_msg4(ffp,FFP_MSG_ARTC_DIRECTCOMPONENTMSG,type,0,data_msg,data_size); return 0; }static int is_realtime(AVFormatContext * s){}メソッドに次のRTSコードブロックを挿入して、ff_ffplay.cファイルを変更します。static int is_realtime(AVFormatContext *s) { if( !strcmp(s->iformat->name, "rtp") || !strcmp(s->iformat->name, "rtsp") || !strcmp(s->iformat->name, "sdp") // *** Beginning of the RTS code block *** || !strcmp(s->iformat->name, "artc") // *** End of the RTS code block *** ) return 1; if(s->pb && ( !strncmp(s->filename, "rtp:", 4) || !strncmp(s->filename, "udp:", 4) ) ) return 1; return 0; }static int read_thread(void * arg){}メソッドに次のRTSコードブロックを挿入して、ff_ffplay.cファイルを変更します。static int read_thread(void *arg) { ...... ic = avformat_alloc_context(); if (!ic) { av_log(NULL, AV_LOG_FATAL, "Could not allocate context.\n"); ret = AVERROR(ENOMEM); goto fail; } // *** Beginning of the RTS code block *** ic->opaque = ffp; ic->control_message_cb = onArtcDemuxerMessage; // *** End of the RTS code block *** ...... if (ffp->skip_calc_frame_rate) { av_dict_set_int(&ic->metadata, "skip-calc-frame-rate", ffp->skip_calc_frame_rate, 0); av_dict_set_int(&ffp->format_opts, "skip-calc-frame-rate", ffp->skip_calc_frame_rate, 0); } // *** Beginning of the RTS code block *** if(strncmp(is->filename, "artc://", 7) == 0) { extern AVInputFormat ff_rtc_demuxer; is->iformat = &ff_rtc_demuxer; } else { if(ffp->iformat_name) is->iformat = av_find_input_format(ffp->iformat_name); } // *** End of the RTS code block *** ...... pkt->flags = 0; // *** Beginning of the RTS code block *** if(strncmp(is->filename, "artc://", 7) == 0) { bool videoExist = is->video_stream >= 0; bool audioExist = is->audio_stream >= 0; // av_log(NULL, AV_LOG_INFO, "videoDuration %lld audioDuration %lld rate %f videoframeQue %d audioFrameque %d\n", // is->videoq.duration, is->audioq.duration, ffp->pf_playback_rate, // frame_queue_nb_remaining(&is->pictq), frame_queue_nb_remaining(&is->sampq)); if(!videoExist) { if(is->audioq.duration > 300 ) { // accelerate if(ffp->pf_playback_rate <= 1.0) { ffp->pf_playback_rate = 1.3; ffp->pf_playback_rate_changed = 1; av_log(NULL, AV_LOG_INFO, "aliyun rts set rate to %f\n", ffp->pf_playback_rate); } } else if(is->audioq.duration < 200) { // restore speed if(ffp->pf_playback_rate > 1.0) { ffp->pf_playback_rate = 1.0; ffp->pf_playback_rate_changed = 1; av_log(NULL, AV_LOG_INFO, "aliyun rts restore rate 1.0\n"); } } } else if((!videoExist || (videoExist && is->videoq.duration > 300)) && (!audioExist || (audioExist && is->audioq.duration > 300))) { if(ffp->pf_playback_rate <= 1) { ffp->pf_playback_rate = 1.3; ffp->pf_playback_rate_changed = 1; av_log(NULL, AV_LOG_INFO, "aliyun rts set rate 1.1\n"); } } else if((videoExist && is->videoq.duration <= 100) || (audioExist && is->audioq.duration <= 100)){ if(ffp->pf_playback_rate > 1) { ffp->pf_playback_rate = 1; ffp->pf_playback_rate_changed = 1; av_log(NULL, AV_LOG_INFO, "aliyun rts set rate 1\n"); } } } // *** End of the RTS code block *** ...... }RTSメッセージのメソッド宣言を追加してff_ffmsg.hファイルを変更します。
#define FFP_MSG_ARTC_DIRECTCOMPONENTMSG 3000RTSメッセージを上位層に送信します。
RTSメッセージの外部呼び出しをリッスンするリスナーの宣言を追加して、IJKMediaPlayback.hファイルを変更します。
IJK_EXTERN NSString *const IJKMPMoviePlayerRtsMsgNotification;RTSメッセージの外部呼び出しをリッスンするリスナーの定義を追加して、IJKMediaPlayback.mファイルを変更します。
NSString *const IJKMPMoviePlayerRtsMsgNotification = @"IJKMPMoviePlayerRtsMsgNotification";リスナーから返されたRTSメッセージを処理するために
postEvent:メソッドを呼び出すことができるように、IJKFFMoviePlayerController.mファイルを変更します。- (void)postEvent: (IJKFFMoviePlayerMessage *)msg { ...... case FFP_MSG_ARTC_DIRECTCOMPONENTMSG:{ NSString *rtsMsg = [[NSString alloc] initWithUTF8String:avmsg->obj]; int type = avmsg->arg1; if (!rtsMsg) { rtsMsg = @""; } NSDictionary *dic = @{@"type":@(type),@"msg":rtsMsg}; [[NSNotificationCenter defaultCenter] postNotificationName:IJKMPMoviePlayerRtsMsgNotification object:dic]; break; } default: // NSLog(@"unknown FFP_MSG_xxx(%d)\n", avmsg->what); break; }アーカイブコンパイラを使用してIJKMediaPlayer.xcodeprojプロジェクトを実行し、最新のIJKMediaFramework.frameworkファイルを取得します。
ijkplayerでネイティブRTS SDKの再試行を実行するために使用するメソッドを追加する
rts_reload_flag変数の定義を追加して、ff_ffplay.hファイルを変更します。
int rts_reload_flag;static int audio_open(FFPlayer * opaque、int64_t wanted_channel_layout、int wanted_nb_channels、int wanted_sample_rate、struct AudioParams * audio_hw_params){}メソッドの後に次のコードブロックを挿入して、ff_ff_ffplay.cファイルを変更します。extern int artc_reload(AVFormatContext *ctx); void ffp_rts_reload(FFPlayer *ffp){ if(rts_reload_flag == 0) { rts_reload_flag = 1; } }static int read_thread(void * arg){}メソッドに次のRTSコードブロックを挿入して、ff_ffplay.cファイルを変更します。static int read_thread(void *arg) { ...... #ifdef FFP_MERGE if (is->paused != is->last_paused) { is->last_paused = is->paused; if (is->paused) is->read_pause_return = av_read_pause(ic); else av_read_play(ic); } #endif // *** Beginning of the RTS code block *** if(rts_reload_flag){ rts_reload_flag = 0; av_log(ffp, AV_LOG_ERROR, "param == ffp_rts_reload\n"); VideoState *is = ffp->is; AVFormatContext *ic = is->ic; artc_reload(ic); } // *** End of the RTS code block *** ...... }ijkmp_rts_reloadメソッドの定義を追加して、ijkplayer.hファイルを変更します。
void ijkmp_rts_reload(IjkMediaPlayer *mp);ijkplayer.cファイルを変更して、ijkmp_rts_reloadメソッドを実装します。
void ijkmp_rts_reload(IjkMediaPlayer *mp) { ffp_rts_reload(mp->ffplayer); }rtsReloadメソッドを上位レイヤーに追加します。
rtsReloadメソッドの外部呼び出しの宣言を追加して、IJKFFMoviePlayerController.hファイルを変更します。
- (void)rtsReload;IJKFFMoviePlayerController.mファイルを変更して、rtsReloadメソッドの外部呼び出しを実装します。
- (void)rtsReload { ijkmp_rts_reload(_mediaPlayer); }アーカイブコンパイラを使用してIJKMediaPlayer.xcodeprojプロジェクトを実行し、最新のIJKMediaFramework.frameworkファイルを取得します。
劣化したプロトコルを介してライブストリームへのRTSメッセージコールバックをリッスンするためのメソッドを呼び出す
RTSメッセージのコールバックをリッスンします。
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(reviceMsg:) name:IJKMPMoviePlayerRtsMsgNotification object:nil];RTSメッセージのコールバックをリッスンするために使用されるメソッドに、劣化ロジックを実装します。
ソースURLのプレフィックスをartc:// からrtmp:// に変更するか、URLをhttp:// xxxx.flv形式に変換してから、ApsaraVideo PlayerのUrlSourceを更新できます。 これにより、劣化したプロトコルを使用してライブストリームを再生できます。
// Use a degraded protocol, such as http://xxx.flv or rtmp://xxx, for live streaming. - (void)convertArtcToRtmpPlay { // Obtain the streaming URL and intercept the prefix of the URL. NSArray *urlSeparated = [self.url componentsSeparatedByString:@"://"]; NSString *urlPrefix = urlSeparated.firstObject; // Check whether the prefix of the URL is artc. If the prefix is artc, you can use a degraded protocol for standard streaming. if ([urlPrefix isEqualToString:@"artc"]) { // http://xxx.flv (recommended) _url = [[@"http://" stringByAppendingString:urlSeparated.lastObject] stringByAppendingString:@".flv"]; // rtmp://xxx // _url = [@"rtmp://" stringByAppendingString:urlSeparated.lastObject]; // Stop playback and destroy the player. [_ijkPlayer stop]; [_ijkPlayer shutdown]; _ijkPlayer = nil; // Re-set the playback source. _ijkPlayer = [[IJKFFMoviePlayerController alloc] initWithContentURL:[NSURL URLWithString:_url] withOptions:options]; ...... // Start playback. dispatch_async(dispatch_get_main_queue(), ^{ [_ijkPlayer prepareToPlay]; [_ijkPlayer play]; }); } }事前にRtsSDK APIをインポートする必要があります。
#import <RtsSDK/rts_messages.h>次に、プレイヤーイベントのコールバックを処理します。
-(void)reviceMsg:(NSNotification*)notification{ NSDictionary *dic = [notification object]; NSNumber *type = dic[@"type"]; switch (type.intValue) { case E_DNS_FAIL: case E_AUTH_FAIL: case E_CONN_TIMEOUT: case E_SUB_TIMEOUT: case E_SUB_NO_STREAM: { // Perform degradation. [self convertArtcToRtmpPlay]; } break; case E_STREAM_BROKEN: { static BOOL retryStartPlay = YES; // The first time the RTS media times out, retry playback once. If the RTS media still times out, use a degraded protocol for playback. if (retryStartPlay) { dispatch_async(dispatch_get_main_queue(), ^{ [_ijkPlayer rtsReload]; }); retryStartPlay = NO; } else { // Perform degradation. [self convertArtcToRtmpPlay]; } } break; case E_RECV_STOP_SIGNAL: { // Stop playback and destroy the player. [_ijkPlayer stop]; [_ijkPlayer shutdown]; _ijkPlayer = nil; } break; default: break; } }

