Topik ini menjelaskan cara mengintegrasikan Native RTS SDK ke dalam pemain pihak ketiga berbasis FFmpeg untuk mengimplementasikan Real-Time Streaming (RTS) pada klien iOS. Topik ini menggunakan pemain pihak ketiga ijkplayer versi k0.8.8.
Prasyarat
Kode sumber ijkplayer telah dikompilasi. Untuk informasi lebih lanjut, lihat file README.md dari ijkplayer.
Prosedur
Unduh dan ekstrak kode sumber ijkplayer. Untuk mendapatkan tautan unduhan, kunjungi halaman ijkplayer.
Unduh dan ekstrak paket Native RTS SDK. Untuk informasi lebih lanjut tentang tautan unduhan, lihat Unduhan SDK dan catatan rilis.
Integrasikan Native RTS SDK sebagai plugin ke dalam ijkplayer. Tabel berikut menjelaskan dua metode integrasi.
Metode Integrasi
Deskripsi
Keuntungan
Kerugian
Perluas FFmpeg.
Perluas FFmpeg dengan menambahkan plugin demuxer.
Metode ini menyederhanakan pengembangan. Anda tidak perlu mengembangkan logika tambahan untuk URL berbasis Alibaba Real-Time Communication (ARTC).
Anda harus mengompilasi ulang FFmpeg.
Perluas ijkplayer.
Perluas ijkplayer dengan menambahkan struct AVInputFormat.
Anda tidak perlu mengompilasi FFmpeg.
Anda harus menambahkan kode logika ke file ff_ffplay.c.
Kompilasi proyek IJKMediaPlayer.
Setelah Anda memperluas FFmpeg atau memperluas ijkplayer, gunakan kompiler Archive untuk menjalankan proyek ijkplayer/ios/IJKMediaPlayer/IJKMediaPlayer.xcodeproj. File IJKMediaFramework.framework dibuat di direktori Products.
Opsional: Jika Anda ingin mendukung aliran melalui protokol HTTPS, sebelum Anda menjalankan kompilasi, pilih Build Phases > Link Binary With Libraries untuk proyek di Xcode dan impor file libssl.a dan libcrypto.a di direktori ijkplayer/ios/build/universal/lib yang dibuat pada langkah-langkah sebelumnya.

Impor file RtsSDK.framework di Native RTS SDK dan file IJKMediaFramework.framework di ijkplayer ke dalam proyek.

Panggil metode yang disediakan oleh ijkplayer untuk menggunakan fitur RTS.
Buat ijkplayer
// Hasilkan URL melalui protokol ARTC. _url = @"artc://xxxx"; // Buat custom playerView. [self.view addSubview:self.playerView]; ... IJKFFOptions *options = [IJKFFOptions optionsByDefault]; // Konfigurasikan decoding perangkat keras. [options setPlayerOptionIntValue:1 forKey:@"videotoolbox"]; // Konfigurasikan decoding perangkat lunak. //[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; // Tentukan mode penskalaan. _ijkPlayer.shouldAutoplay = YES; // Aktifkan putar otomatis. [self.playerView addSubview:_ijkPlayer.view];Atur kontrol pemutaran
Mulai pemutaran.
Anda harus memanggil metode ini di utas utama. Kami sarankan Anda menghentikan pemutaran dan melepaskan pemain sebelum Anda memulai pemutaran setiap kali.
dispatch_async(dispatch_get_main_queue(), ^{ [_ijkPlayer prepareToPlay]; [_ijkPlayer play]; });Hentikan pemutaran dan lepaskan pemain.
// Hentikan pemutaran. [_ijkPlayer stop]; // Lepaskan pemain. [_ijkPlayer shutdown]; _ijkPlayer = nil;
Dengarkan event RTS
Dengarkan callback pesan Native RTS SDK di ijkplayer
Buka proyek ijkplayer/ios/IJKMediaPlayer/IJKMediaPlayer.xcodeproj dan tambahkan metode yang sesuai ke file-file terkait.
Modifikasi file ff_ffplay.c dengan menyisipkan blok kode berikut setelah metode
static int audio_open(FFPlayer *opaque, int64_t wanted_channel_layout, int wanted_nb_channels, int wanted_sample_rate, struct AudioParams *audio_hw_params){}.extern int artcDemuxerMessage(struct AVFormatContext *s, int type, void *data, size_t data_size); //aliyun rts:menerima pesan artc 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:kirim pesan ke aplikasi 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; }Modifikasi file ff_ffplay.c dengan menyisipkan blok kode RTS berikut ke dalam metode
static int is_realtime(AVFormatContext *s){}.static int is_realtime(AVFormatContext *s) { if( !strcmp(s->iformat->name, "rtp") || !strcmp(s->iformat->name, "rtsp") || !strcmp(s->iformat->name, "sdp") // *** Awal blok kode RTS *** || !strcmp(s->iformat->name, "artc") // *** Akhir blok kode RTS *** ) return 1; if(s->pb && ( !strncmp(s->filename, "rtp:", 4) || !strncmp(s->filename, "udp:", 4) ) ) return 1; return 0; }Modifikasi file ff_ffplay.c dengan menyisipkan blok kode RTS berikut ke dalam metode
static int read_thread(void *arg){}.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; } // *** Awal blok kode RTS *** ic->opaque = ffp; ic->control_message_cb = onArtcDemuxerMessage; // *** Akhir blok kode RTS *** ...... 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); } // *** Awal blok kode RTS *** 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); } // *** Akhir blok kode RTS *** ...... pkt->flags = 0; // *** Awal blok kode RTS *** 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 ) { // percepat 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) { // pulihkan kecepatan 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"); } } } // *** Akhir blok kode RTS *** ...... }Modifikasi file ff_ffmsg.h dengan menambahkan deklarasi metode untuk pesan RTS.
#define FFP_MSG_ARTC_DIRECTCOMPONENTMSG 3000Kirim pesan RTS ke lapisan atas.
Modifikasi file IJKMediaPlayback.h dengan menambahkan deklarasi listener yang mendengarkan panggilan eksternal pesan RTS.
IJK_EXTERN NSString *const IJKMPMoviePlayerRtsMsgNotification;Modifikasi file IJKMediaPlayback.m dengan menambahkan definisi listener yang mendengarkan panggilan eksternal pesan RTS.
NSString *const IJKMPMoviePlayerRtsMsgNotification = @"IJKMPMoviePlayerRtsMsgNotification";Modifikasi file IJKFFMoviePlayerController.m agar Anda dapat memanggil metode
postEvent:untuk memproses pesan RTS yang dikembalikan oleh listener.- (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; }Gunakan kompiler Archive untuk menjalankan proyek IJKMediaPlayer.xcodeproj untuk mendapatkan file IJKMediaFramework.framework terbaru.
Tambahkan metode yang digunakan untuk melakukan percobaan ulang untuk Native RTS SDK di ijkplayer
Modifikasi file ff_ffplay.h dengan menambahkan definisi variabel rts_reload_flag.
int rts_reload_flag;Modifikasi file ff_ffplay.c dengan menyisipkan blok kode berikut setelah metode
static int audio_open(FFPlayer *opaque, int64_t wanted_channel_layout, int wanted_nb_channels, int wanted_sample_rate, struct AudioParams *audio_hw_params){}.extern int artc_reload(AVFormatContext *ctx); void ffp_rts_reload(FFPlayer *ffp){ if(rts_reload_flag == 0) { rts_reload_flag = 1; } }Modifikasi file ff_ffplay.c dengan menyisipkan blok kode RTS berikut ke dalam metode
static int read_thread(void *arg){}.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 // *** Awal blok kode RTS *** 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); } // *** Akhir blok kode RTS *** ...... }Modifikasi file ijkplayer.h dengan menambahkan definisi metode ijkmp_rts_reload.
void ijkmp_rts_reload(IjkMediaPlayer *mp);Modifikasi file ijkplayer.c untuk mengimplementasikan metode ijkmp_rts_reload.
void ijkmp_rts_reload(IjkMediaPlayer *mp) { ffp_rts_reload(mp->ffplayer); }Tambahkan metode rtsReload ke lapisan atas.
Modifikasi file IJKFFMoviePlayerController.h dengan menambahkan deklarasi panggilan eksternal metode rtsReload.
- (void)rtsReload;Modifikasi file IJKFFMoviePlayerController.m untuk mengimplementasikan panggilan eksternal metode rtsReload.
- (void)rtsReload { ijkmp_rts_reload(_mediaPlayer); }Gunakan kompiler Archive untuk menjalankan proyek IJKMediaPlayer.xcodeproj untuk mendapatkan file IJKMediaFramework.framework terbaru.
Panggil metode untuk mendengarkan callback pesan RTS untuk streaming langsung melalui protokol degradasi
Dengarkan callback pesan RTS.
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(reviceMsg:) name:IJKMPMoviePlayerRtsMsgNotification object:nil];Implementasikan logika degradasi di metode yang digunakan untuk mendengarkan callback pesan RTS.
Anda dapat mengubah prefiks URL sumber dari artc:// menjadi rtmp:// atau mengonversi URL ke format http://xxxx.flv, lalu memperbarui UrlSource di Pemutar Video Apsara. Dengan cara ini, Anda dapat menggunakan protokol degradasi untuk memainkan streaming langsung.
// Gunakan protokol degradasi, seperti http://xxx.flv atau rtmp://xxx, untuk streaming langsung. - (void)convertArtcToRtmpPlay { // Dapatkan URL streaming dan potong prefiks URL. NSArray *urlSeparated = [self.url componentsSeparatedByString:@"://"]; NSString *urlPrefix = urlSeparated.firstObject; // Periksa apakah prefiks URL adalah artc. Jika prefiksnya adalah artc, Anda dapat menggunakan protokol degradasi untuk streaming standar. if ([urlPrefix isEqualToString:@"artc"]) { // http://xxx.flv (direkomendasikan) _url = [[@"http://" stringByAppendingString:urlSeparated.lastObject] stringByAppendingString:@".flv"]; // rtmp://xxx // _url = [@"rtmp://" stringByAppendingString:urlSeparated.lastObject]; // Hentikan pemutaran dan hancurkan pemain. [_ijkPlayer stop]; [_ijkPlayer shutdown]; _ijkPlayer = nil; // Atur ulang sumber pemutaran. _ijkPlayer = [[IJKFFMoviePlayerController alloc] initWithContentURL:[NSURL URLWithString:_url] withOptions:options]; ...... // Mulai pemutaran. dispatch_async(dispatch_get_main_queue(), ^{ [_ijkPlayer prepareToPlay]; [_ijkPlayer play]; }); } }Anda harus mengimpor API RtsSDK terlebih dahulu.
#import <RtsSDK/rts_messages.h>Kemudian proses callback event pemain.
-(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: { // Lakukan degradasi. [self convertArtcToRtmpPlay]; } break; case E_STREAM_BROKEN: { static BOOL retryStartPlay = YES; // Pertama kali media RTS timeout, coba putar ulang sekali. Jika media RTS masih timeout, gunakan protokol degradasi untuk pemutaran. if (retryStartPlay) { dispatch_async(dispatch_get_main_queue(), ^{ [_ijkPlayer rtsReload]; }); retryStartPlay = NO; } else { // Lakukan degradasi. [self convertArtcToRtmpPlay]; } } break; case E_RECV_STOP_SIGNAL: { // Hentikan pemutaran dan hancurkan pemain. [_ijkPlayer stop]; [_ijkPlayer shutdown]; _ijkPlayer = nil; } break; default: break; } }





