Tema ini menjelaskan cara mengintegrasikan Native RTS SDK dengan pemain berbasis FFmpeg di Android untuk mengimplementasikan RTS, menggunakan tag k0.8.8 dari ijkplayer sebagai contoh.
Prasyarat
Kode sumber ijkplayer telah dikompilasi. Untuk informasi lebih lanjut, lihat file README.md di repositori ijkplayer.
Prosedur
-
Unduh dan ekstrak kode sumber ijkplayer. Untuk tautan unduhan, lihat ijkplayer.
-
Unduh dan ekstrak Native RTS SDK. Untuk tautan unduhan, lihat Catatan rilis.
-
Modifikasi skrip kompilasi ijkplayer.
-
Modifikasi ijkplayer-android/init-android.sh. Dalam pemanggilan `pull_fork`, pertahankan hanya arsitektur armv7a dan arm64.

-
Modifikasi ijkplayer-android/config/module-lite.sh untuk mendukung decoding Pulse-Code Modulation (PCM). Native RTS SDK mengeluarkan data PCM.
# aliyun rts export COMMON_FF_CFG_FLAGS="$COMMON_FF_CFG_FLAGS --enable-decoder=pcm_s16be_planar" export COMMON_FF_CFG_FLAGS="$COMMON_FF_CFG_FLAGS --enable-decoder=pcm_s16le" export COMMON_FF_CFG_FLAGS="$COMMON_FF_CFG_FLAGS --enable-decoder=pcm_s16le_planar" -
Modifikasi ijkplayer-android/android/contrib/compile-ffmpeg.sh. Untuk `FF_ACT_ARCHS_64`, pertahankan hanya arsitektur armv7a dan arm64.
FF_ACT_ARCHS_64="armv7a arm64" -
Modifikasi ijkplayer-android/android/compile-ijk.sh. Untuk `ACT_ABI_64`, pertahankan hanya arsitektur armv7a dan arm64.
ACT_ABI_64="armv7a arm64"
-
-
Integrasikan Native RTS SDK ke dalam ijkplayer sebagai plugin. Anda dapat menggunakan salah satu dari dua metode berikut:
Metode integrasi
Deskripsi
Keuntungan
Kerugian
Perluas FFmpeg
Perluas demuxer FFmpeg di ijkplayer.
Metode ini sederhana. Anda tidak perlu menambahkan logika terpisah untuk URL Alibaba Real-Time Communication (ARTC).
Anda harus mengkompilasi ulang pustaka FFmpeg.
Perluas ijkplayer
Tambahkan AVInputFormat ke ijkplayer.
Anda tidak perlu mengkompilasi FFmpeg.
Anda harus menambahkan kode logika ke file ff_ffplay.c.
-
Integrasikan ijkplayer ke dalam proyek Anda.
-
Salin file RtsNetSDK.jar dari Native RTS SDK ke proyek Anda.
-
Impor pustaka dinamis Native RTS SDK ke dalam Activity yang digunakan untuk pemutaran RTS.
static { System.loadLibrary("RtsSDK"); } -
Impor modul ijkplayer-arm64, ijkplayer-armv7a, dan ijkplayer-java.

-
-
Panggil API ijkplayer untuk mengimplementasikan fitur RTS.
-
Buat instance ijkplayer
mIjkPlayer = new IjkMediaPlayer(); -
Atur callback
mIjkPlayer.setOnPreparedListener(new IMediaPlayer.OnPreparedListener() { @Override public void onPrepared(IMediaPlayer iMediaPlayer) { } }); mIjkPlayer.setOnInfoListener(new IMediaPlayer.OnInfoListener() { @Override public boolean onInfo(IMediaPlayer iMediaPlayer, int arg1, int arg2) { }); mIjkPlayer.setOnVideoSizeChangedListener(new IMediaPlayer.OnVideoSizeChangedListener() { @Override public void onVideoSizeChanged(IMediaPlayer iMediaPlayer, int width, int height, int sarNum, int sarDen) { } }); mIjkPlayer.setOnErrorListener(new IMediaPlayer.OnErrorListener() { @Override public boolean onError(IMediaPlayer iMediaPlayer, int framework_err, int impl_err) { return false; } }); -
Atur jendela tampilan
mIjkPlayer.setSurface(surface); -
Atur sumber pemutaran
mIjkPlayer.setDataSource("artc://<streaming URL>"); -
Kontrol status
public void prepare() { if(mIjkPlayer != null){ mIjkPlayer.prepareAsync(); } } @Override public void start() { if(mIjkPlayer != null){ mIjkPlayer.start(); } } public void stop() { if(mIjkPlayer != null){ mIjkPlayer.stop(); } } public void release() { if(mIjkPlayer != null){ mIjkPlayer.release(); } }
-
Dengarkan event RTS
Dengarkan callback pesan dari Native RTS SDK di ijkplayer
Buka proyek ijkplayer/android/ijkplayer.
Modifikasi file ff_ffplay.c. Sisipkan 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: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; }Modifikasi file ff_ffplay.c. Sisipkan 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") // ***rts code block begin*** || !strcmp(s->iformat->name, "artc") // ***rts code block end*** ) return 1; if(s->pb && ( !strncmp(s->filename, "rtp:", 4) || !strncmp(s->filename, "udp:", 4) ) ) return 1; return 0; }Modifikasi file ff_ffplay.c. Sisipkan 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; } // ***rts code block begin*** ic->opaque = ffp; ic->control_message_cb = onArtcDemuxerMessage; // ***rts code block end*** ...... 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); } // ***rts code block begin*** 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); } // ***rts code block end*** ...... pkt->flags = 0; // ***rts code block begin*** 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"); } } } // ***rts code block end*** ...... }Modifikasi file ff_ffmsg.h untuk menambahkan deklarasi antarmuka pesan RTS.
#define FFP_MSG_ARTC_DIRECTCOMPONENTMSG 3000Kirim pesan RTS ke lapisan atas.
Modifikasi file ijkplayer_android_def.h untuk menambahkan jenis enumerasi pesan RTS.
enum media_event_type { // Other code is omitted. MEDIA_ARTC_MESSAGE = 3000, // aliyun rts : msg info key };Modifikasi file ijkplayer_jni.c untuk mengirim pesan RTS ke lapisan atas.
static void message_loop_n(JNIEnv *env, IjkMediaPlayer *mp) { //... Other code is omitted. while (1) { switch (msg.what) { //aliyun rts: post artc event case FFP_MSG_ARTC_DIRECTCOMPONENTMSG: if (msg.obj) { const char * result = (const char *)msg.obj; ALOGE("aliyun rts : FFP_MSG_ARTC_DIRECTCOMPONENTMSG = %d , %s\n", msg.arg1,result); jstring data_msg = (*env)->NewStringUTF(env, result); post_event2(env, weak_thiz, MEDIA_ARTC_MESSAGE, msg.arg1, 0, data_msg); J4A_DeleteLocalRef__p(env, &data_msg); } else { post_event2(env, weak_thiz, MEDIA_ARTC_MESSAGE, 0, 0, NULL); } break; } } }Modifikasi file IjkMediaPlayer.java untuk menerima pesan RTS.
private static class EventHandler extends Handler { @Override public void handleMessage(Message msg) { IjkMediaPlayer player = mWeakPlayer.get(); if (player == null || player.mNativeMediaPlayer == 0) { DebugLog.w(TAG, "IjkMediaPlayer went away with unhandled events"); return; } switch (msg.what) { //aliyun artc:post event case MEDIA_ARTC_MESSAGE: if(msg.arg1 == FFP_ARTC_CONNECT_LOST){ player.notifyOnARTCMessage("NetWorkDisconnect",0,""); }else if(msg.arg1 == FFP_ARTC_RECOVERED){ player.notifyOnARTCMessage("NetWorkRetrySuccess",0,""); }else{ if(msg.obj == null){ player.notifyOnARTCMessage("DirectComponentMSG",0, ""); }else{ String result = (String) msg.obj; result = result.replaceAll("\"",""); player.mAliyunRTSMsgBuilder = new StringBuilder("{\"content\":"); player.mAliyunRTSMsgBuilder.append("\"").append(result).append("\"}"); player.notifyOnARTCMessage("DirectComponentMSG",0, (String) result); } } break; } } }Tambahkan callback pesan RTS ke file IMediaPlayer.java.
//aliyun rts:artc message callback interface OnARTCMessageListener{ void onMessage(String name,int externValue,String extraMsg); }Tambahkan pengaturan listener ke file AbstractMediaPlayer.java.
//aliyun rts : set artc message listener public final void setOnARTCMessageListener(OnARTCMessageListener listener){ this.mOnARTCMessageListener = listener; } //aliyun rts : call back protected final void notifyOnARTCMessage(String name, int externValue, String extraMsg){ if(mOnARTCMessageListener != null){ mOnARTCMessageListener.onMessage(name, externValue, extraMsg); } }Jalankan skrip ./compile-ijk.sh untuk mengompilasi ulang.
Tambahkan metode coba ulang untuk Native RTS SDK di ijkplayer
Modifikasi file ff_ffplay.h untuk menambahkan definisi variabel `rts_reload_flag`.
int rts_reload_flag;Modifikasi file ff_ffplay.c. Sisipkan blok metode 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. Sisipkan 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 // ***rts code block begin*** 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); } // ***rts code block end*** ...... }Modifikasi file ijkplayer.h untuk 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 antarmuka `rtsReload` ke lapisan atas.
Modifikasi file ijkplayer_jni.c untuk menambahkan antarmuka `reload`.
//aliyun rts : reload native static void IjkMediaPlayer_reload(JNIEnv *env,jobject thiz) { IjkMediaPlayer *mp = jni_get_media_player(env,thiz); ijkmp_rts_reload(mp); LABEL_RETURN: ijkmp_dec_ref_p(&mp); } static JNINativeMethod g_methods[] = { //aliyun rts: reload { "_reload", "()V", (void *) IjkMediaPlayer_reload }, }Modifikasi file IMediaPlayer.java untuk menambahkan antarmuka `reload`.
//aliyun rts: reload void reload();Modifikasi file IjkMediaPlayer.java untuk memanggil metode asli `reload`.
//aliyun rts : native method private native void _reload(); @Override public void reload() { _reload(); }
Gunakan callback pesan RTS untuk mengimplementasikan degradasi aliran
Dengarkan callback pesan RTS.
mIjkPlayer.setOnARTCMessageListener(new IMediaPlayer.OnARTCMessageListener() { @Override public void onMessage(String name, int externValue, String extraMsg) {} });Implementasikan logika degradasi aliran dalam metode callback pesan RTS.
Degradasi aliran adalah strategi di mana Anda mengubah awalan string URL sumber pemain dari `artc://` menjadi `rtmp://` atau `http://xxxx.flv`, memperbarui URL sumber pemain, dan kemudian memulai ulang pemutaran.
if (SOURCE_URL.startsWith("artc://")) { mIjkPlayer.stop();// Hentikan pemutaran. mIjkPlayer.reset();// Reset pemain. Jika Anda menggunakan kembali ijkplayer tanpa meresetnya, aplikasi akan crash. mIjkPlayer.setDataSource("http://xxx.flv"); mIjkPlayer.prepareAsync(); }Selama startup pemutaran atau transmisi langsung, callback peristiwa pemain mungkin menerima pesan dari komponen pemutaran. Parsing string JSON deskripsi peristiwa. Bidang `code` berisi pesan dari RTS SDK. Jika pesannya adalah `E_DNS_FAIL`, `E_AUTH_FAIL`, `E_CONN_TIMEOUT`, `E_SUB_TIMEOUT`, atau `E_SUB_NO_STREAM`, turunkan aliran. Jika pesannya adalah `E_STREAM_BROKEN`, coba ulang pemutaran sekali. Jika pesan diterima lagi, turunkan aliran. Jika pesannya adalah `E_RECV_STOP_SIGNAL`, hentikan pemutaran secara langsung. Degradasi aliran tidak diperlukan.
// Kelas enumerasi informasi Rts kustom public enum RtsError { /** * Gagal resolusi DNS. */ E_DNS_FAIL(20001), /** * Otentikasi gagal. */ E_AUTH_FAIL(20002), /** * Waktu habis sinyal koneksi. */ E_CONN_TIMEOUT(20011), /** * Sinyal langganan mengembalikan kesalahan atau waktu habis. */ E_SUB_TIMEOUT(20012), /** * Aliran yang dilanggan tidak ada. */ E_SUB_NO_STREAM(20013), /** * Timeout media. Tidak ada paket audio atau video yang diterima. */ E_STREAM_BROKEN(20052), /** * Menerima sinyal berhenti dari CDN. */ E_RECV_STOP_SIGNAL(20061); private int code; RtsError(int code) { this.code = code; } public int getCode() { return code; } public void setCode(int code) { this.code = code; } } mIjkPlayer.setOnARTCMessageListener(new IMediaPlayer.OnARTCMessageListener() { @Override public void onMessage(String name, int externValue, String msg) { if("DirectComponentMSG".equals(name)){ if (msg.contains("code=" + RtsError.E_DNS_FAIL.getCode()) || msg.contains("code=" + RtsError.E_AUTH_FAIL.getCode()) || msg.contains("code=" + RtsError.E_CONN_TIMEOUT.getCode()) || msg.contains("code=" + RtsError.E_SUB_TIMEOUT.getCode()) || msg.contains("code=" + RtsError.E_SUB_NO_STREAM.getCode()) || msg.contains("code=" + RtsError.E_STREAM_BROKEN.getCode()) || msg.contains("code=" + RtsError.E_RECV_STOP_SIGNAL.getCode())) { //TODO: Implementasikan logika degradasi. } } } });





