Android平台RTSP|RTMP播放器技术实践:基于大牛直播SDK的深度探索

前言

在移动直播、视频监控等场景中,RTSP(Real Time Streaming Protocol)和 RTMP(Real Time Messaging Protocol)是两种常见的流媒体传输协议。它们能够提供实时、低延迟的音视频传输,但实现高效的播放功能具有一定技术门槛。大牛直播SDK作为行业内备受认可的解决方案,提供了功能强大、性能卓越的 RTSP/RTMP 播放模块。本文将基于大牛直播 SDK,详细讲解如何在 Android 平台开发一个高效的 RTSP|RTMP 播放器。

SDK 集成准备

环境配置

  • 系统支持:确保目标设备运行 Android 5.1 及以上版本,支持的 CPU 架构包括 armv7、arm64、x86 和 x86_64。

  • 依赖集成

    • Smartavengine.jar 添加至项目依赖。

    • 拷贝对应架构的 libSmartPlayer.so 文件至 jniLibs 文件夹。

    • AndroidManifest.xml 中声明必要权限:

  • SO 库加载 :在代码中通过 System.loadLibrary("SmartPlayer"); 加载原生库。

核心功能实现

Window平台采集毫秒计数器窗口,然后编码打包注入到轻量级RTSP服务,对外提供拉流的RTSP URL,然后Android平台同时播放4路RTSP流,整体延迟如下:

功能支持

  • 音频:AAC/Speex(RTMP)/PCMA/PCMU;
  • 视频:H.264、H.265;
  • 播放协议:RTSP|RTMP;
  • 支持纯音频、纯视频、音视频播放;
  • 支持多实例播放;
  • 支持软解码,特定机型硬解码;
  • 支持RTSP TCP、UDP模式设置;
  • 支持RTSP TCP、UDP模式自动切换;
  • 支持RTSP超时时间设置,单位:秒;
  • 支持buffer时间设置,单位:毫秒;
  • 支持超低延迟模式;
  • 支持断网自动重连、视频追赶,支持buffer状态等回调;
  • 支持视频view实时旋转(0° 90° 180° 270°);
  • 支持视频view水平反转、垂直反转;
  • 支持Surfaceview/OpenGL ES/TextureView绘制;
  • 支持视频画面填充模式设置;
  • 音频支持AudioTrack、OpenSL ES模式;
  • 支持jpeg、png实时截图;
  • 支持实时音量调节;
  • 支持解码前音视频数据回调;
  • 支持解码后YUV/RGB数据回调;
  • 支持Enhanced RTMP;
  • 支持扩展录像功能;
  • 支持Android 5.1及以上版本。

播放器初始化与播放控制

ini 复制代码
SmartPlayerJniV2 libPlayer = new SmartPlayerJniV2();
long playerHandle = libPlayer.SmartPlayerOpen(context);
if (playerHandle == 0) {
    Log.e("PlayerDemo", "Failed to initialize player");
    return;
}

// 设置播放参数
libPlayer.SmartPlayerSetBuffer(playerHandle, 200); // 设置缓冲时间 200ms
libPlayer.SmartPlayerSetUrl(playerHandle, "rtmp://example.com/live/stream");

// 设置 SurfaceView 用于视频渲染
libPlayer.SmartPlayerSetSurface(playerHandle, surfaceView);

// 开始播放
int result = libPlayer.SmartPlayerStartPlay(playerHandle);
if (result != 0) {
    Log.e("PlayerDemo", "Failed to start playback");
}

事件回调处理

通过实现 NTSmartEventCallbackV2 接口,可以接收播放器状态更新等事件:

kotlin 复制代码
    class EventHandeV2 implements NTSmartEventCallbackV2 {
        @Override
        public void onNTSmartEventCallbackV2(long handle, int id, long param1,
                                             long param2, String param3, String param4, Object param5) {

            //Log.i(TAG, "EventHandeV2: handle=" + handle + " id:" + id);

            String player_event = "";

            switch (id) {
                case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_STARTED:
                    player_event = "开始..";
                    break;
                case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_CONNECTING:
                    player_event = "连接中..";
                    break;
                case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_CONNECTION_FAILED:
                    player_event = "连接失败..";
                    break;
                case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_CONNECTED:
                    player_event = "连接成功..";
                    break;
                case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_DISCONNECTED:
                    player_event = "连接断开..";
                    break;
                case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_STOP:
                    player_event = "停止播放..";
                    break;
                case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_RESOLUTION_INFO:
                    player_event = "分辨率信息: width: " + param1 + ", height: " + param2;
                    break;
                case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_NO_MEDIADATA_RECEIVED:
                    player_event = "收不到媒体数据,可能是url错误..";
                    break;
                case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_SWITCH_URL:
                    player_event = "切换播放URL..";
                    break;
                case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_CAPTURE_IMAGE:
                    player_event = "快照: " + param1 + " 路径:" + param3;

                    if (param1 == 0)
                        player_event = player_event + ", 截取快照成功";
                     else
                        player_event = player_event + ", 截取快照失败";

                    if (param4 != null && !param4.isEmpty())
                        player_event += (", user data:" + param4);

                    break;

                case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_RECORDER_START_NEW_FILE:
                    player_event = "[record]开始一个新的录像文件 : " + param3;
                    break;
                case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_ONE_RECORDER_FILE_FINISHED:
                    player_event = "[record]已生成一个录像文件 : " + param3;
                    break;

                case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_START_BUFFERING:
                    Log.i(TAG, "Start Buffering");
                    break;

                case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_BUFFERING:
                    Log.i(TAG, "Buffering:" + param1 + "%");
                    break;

                case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_STOP_BUFFERING:
                    Log.i(TAG, "Stop Buffering");
                    break;

                case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_DOWNLOAD_SPEED:
                    player_event = "download_speed:" + param1 + "Byte/s" + ", "
                            + (param1 * 8 / 1000) + "kbps" + ", " + (param1 / 1024)
                            + "KB/s";
                    break;

                case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_RTSP_STATUS_CODE:
                    Log.e(TAG, "RTSP error code received, please make sure username/password is correct, error code:" + param1);
                    player_event = "RTSP error code:" + param1;
                    break;
            }

            if (player_event.length() > 0) {
                Log.i(TAG, player_event);
                Message message = new Message();
                message.what = PLAYER_EVENT_MSG;
                message.obj = player_event;
                handler.sendMessage(message);
            }
        }
    }

设置回调:

arduino 复制代码
libPlayer.SetSmartPlayerEventCallbackV2(playerHandle, new EventCallback());

录像功能集成

csharp 复制代码
@SuppressLint("NewApi")
void ConfigRecorderFuntion() {
	if (libPlayer != null) {
		int is_rec_trans_code = 1;
		libPlayer.SmartPlayerSetRecorderAudioTranscodeAAC(playerHandle, is_rec_trans_code);

		if (recDir != null && !recDir.isEmpty()) {
			int ret = libPlayer.SmartPlayerCreateFileDirectory(recDir);
			if (0 == ret) {
				if (0 != libPlayer.SmartPlayerSetRecorderDirectory(
						playerHandle, recDir)) {
					Log.e(TAG, "Set recoder dir failed , path:" + recDir);
					return;
				}

				if (0 != libPlayer.SmartPlayerSetRecorderFileMaxSize(
						playerHandle, 200)) {
					Log.e(TAG,
							"SmartPublisherSetRecorderFileMaxSize failed.");
					return;
				}

			} else {
				Log.e(TAG, "Create recorder dir failed, path:" + recDir);
			}
		}
	}
}

实时截图功能

ini 复制代码
btnCaptureImage.setOnClickListener(new Button.OnClickListener() {
	@SuppressLint("SimpleDateFormat")
	public void onClick(View v) {
		if (0 == playerHandle)
			return;

		if (null == capture_image_date_format_)
			capture_image_date_format_ = new SimpleDateFormat("yyyyMMdd_HHmmss_SSS");

		String timestamp = capture_image_date_format_.format(new Date());
		String imageFileName = timestamp;

		String image_path = imageSavePath + "/" + imageFileName;

		int quality;
		boolean is_jpeg = true;
		if (is_jpeg) {
			image_path += ".jpeg";
			quality = 100;
		}
		else {
			image_path += ".png";
			quality = 100;
		}

		int capture_ret = libPlayer.CaptureImage(playerHandle,is_jpeg?0:1, quality, image_path, "test cix");
		Log.i(TAG, "capture image ret:" + capture_ret + ", file:" + image_path);
	}
});

视频画面控制

scss 复制代码
// 水平翻转
libPlayer.SmartPlayerSetFlipHorizontal(playerHandle, 1);

// 垂直翻转
libPlayer.SmartPlayerSetFlipVertical(playerHandle, 1);

// 旋转 90 度
libPlayer.SmartPlayerSetRotation(playerHandle, 90);

性能优化与最佳实践

  • 硬解码优先:在支持的设备上优先启用硬解码(H.264/H.265),可显著降低 CPU 负载。

  • 缓冲策略调整:根据网络条件动态调整缓冲区大小,平衡延迟与流畅度。

  • 多实例管理:通过封装播放器实例,支持多路视频同时播放。

    /*

    • LibPlayerWrapper.java
    • Created by daniusdk.com
    • WeChat: xinsheng120 */ package com.daniulive.smartplayer;

    import android.content.Context; import android.util.Log; import android.view.Surface; import android.view.SurfaceView; import android.view.View;

    import com.eventhandle.NTSmartEventCallbackV2; import java.lang.ref.WeakReference; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock;

    public class LibPlayerWrapper { private static String TAG = "NTLogLibPlayerW"; private static final int OK = 0;

    ini 复制代码
    private WeakReference<Context> context_;
    private final ReadWriteLock rw_lock_ = new ReentrantReadWriteLock(true);
    private final java.util.concurrent.locks.Lock write_lock_ = rw_lock_.writeLock();
    private final java.util.concurrent.locks.Lock read_lock_ = rw_lock_.readLock();
    
    private SmartPlayerJniV2 lib_player_;
    private volatile long native_handle_;
    private View surface_view_;
    
    private volatile boolean is_playing_;
    private volatile boolean is_recording_;
    
    private EventListener event_listener_;
    
    public LibPlayerWrapper(SmartPlayerJniV2 lib_player, Context context, EventListener listener) {
        if (!empty())
            throw new IllegalStateException("it is not empty");
    
        if (null == lib_player)
            throw new NullPointerException("lib_player is null");
    
        this.lib_player_ = lib_player;
    
        if (context != null)
            this.context_ = new WeakReference<>(context);
    
        this.event_listener_ = listener;
    }
    
    private void clear_all_playing_flags() {
        this.is_playing_ = false;
        this.is_recording_ = false;
    }
    
    public void set(long handle) {
        if (!empty())
            throw new IllegalStateException("it is not empty");
    
        write_lock_.lock();
        try {
            clear_all_playing_flags();
            this.native_handle_ = handle;
        } finally {
            write_lock_.unlock();
        }
    
        Log.i(TAG, "set native_handle:" + handle);
    }
    
    @Override
    protected void finalize() throws Throwable {
        try {
            if (check_native_handle()) {
                if(is_playing()) {
                    lib_player_.SmartPlayerStopPlay(get());
                    this.is_playing_ = false;
                }
    
                if(is_recording()) {
                    lib_player_.SmartPlayerStopRecorder(get());
                    this.is_recording_ = false;
                }
    
                lib_player_.SmartPlayerClose(this.native_handle_);
                Log.i(TAG, "finalize close handle:" + this.native_handle_);
                this.native_handle_ = 0;
            }
        }catch (Exception e) {
    
        }
    
        super.finalize();
    }
    
    public void release() {
        if (empty())
            return;
    
        if(is_playing())
            stopPlayer();
    
        if (is_recording())
            stopRecorder();
    
        long handle;
        write_lock_.lock();
        try {
            handle = this.native_handle_;
            this.native_handle_ = 0;
            clear_all_playing_flags();
        } finally {
            write_lock_.unlock();
        }
    
        if (lib_player_ != null && handle != 0)
            lib_player_.SmartPlayerClose(handle);
    
        event_listener_ = null;
    }
    
    public boolean try_release() {
        if (empty())
            return false;
    
        if (is_player_running()) {
            Log.i(TAG, "try_release it is running, native_handle:" + get());
            return false;
        }
    
        long handle;
        write_lock_.lock();
        try {
            if (is_player_running())
                return false;
    
            handle = this.native_handle_;
            this.native_handle_ = 0;
        } finally {
            write_lock_.unlock();
        }
    
        if (lib_player_ != null && handle != 0)
            lib_player_.SmartPlayerClose(handle);
    
        return true;
    }
    
    public final boolean empty() { return 0 == this.native_handle_; }
    
    public final long get() { return this.native_handle_; }
    
    public View get_view() {return this.surface_view_;}
    
    public final boolean check_native_handle() {
        return this.lib_player_ != null && this.native_handle_ != 0;
    }
    
    public final boolean is_playing() { return is_playing_; }
    
    public final boolean is_recording() { return is_recording_; }
    
    public final boolean is_player_running() { return is_playing_ || is_recording_; }
    
    private boolean isValidRtspOrRtmpUrl(String url) {
        if (url == null || url.isEmpty()) {
            return false;
        }
        return url.trim().startsWith("rtsp://") || url.startsWith("rtmp://");
    }
    
    private EventListener getListener() {
        return this.event_listener_;
    }
    
    private Context application_context() {
        return context_ == null ? null : context_.get();
    }
    
    public boolean initialize(String playback_url, int play_buffer, int is_using_tcp) {
    
        if (check_native_handle())
            return true;
    
        if(!isValidRtspOrRtmpUrl(playback_url))
            return false;
    
        long handle = lib_player_.SmartPlayerOpen(application_context());
        if (0==handle) {
            Log.e(TAG, "sdk open failed!");
            return false;
        }
    
        set(handle);
    
        configurePlayer(playback_url, play_buffer, is_using_tcp);
    
        return true;
    }
    
    private void configurePlayer(String playback_url, int buffer, int is_using_tcp) {
    
        lib_player_.SetSmartPlayerEventCallbackV2(get(), new EventHandleV2());
    
        lib_player_.SmartPlayerSetBuffer(get(), buffer);
    
        // set report download speed(默认2秒一次回调 用户可自行调整report间隔)
        lib_player_.SmartPlayerSetReportDownloadSpeed(get(), 1, 4);
    
        boolean isFastStartup = true;
        lib_player_.SmartPlayerSetFastStartup(get(), isFastStartup ? 1 : 0);
    
        //设置RTSP超时时间
        int rtsp_timeout = 10;
        lib_player_.SmartPlayerSetRTSPTimeout(get(), rtsp_timeout);
    
        //设置RTSP TCP/UDP模式自动切换
        int is_auto_switch_tcp_udp = 1;
        lib_player_.SmartPlayerSetRTSPAutoSwitchTcpUdp(get(), is_auto_switch_tcp_udp);
    
        lib_player_.SmartPlayerSaveImageFlag(get(), 1);
    
        // It only used when playback RTSP stream..
        lib_player_.SmartPlayerSetRTSPTcpMode(get(), is_using_tcp);
    
        lib_player_.DisableEnhancedRTMP(get(), 0);
    
        lib_player_.SmartPlayerSetUrl(get(), playback_url);
    
        //try_set_rtsp_url(playback_url);
    }
    
    /*
     *这里尝试解析rtsp url, 然后设置给sdk, 考虑到各种不规范的URL, 这里解析不一定正确,请按实际情况自行实现
     */
    private void try_set_rtsp_url(String in_url) {
        if (null == in_url)
            return;
    
        final String rtsp_prefix = "rtsp://";
    
        String url = in_url.trim();
        if (url.isEmpty() || !url.startsWith(rtsp_prefix))
            return;
    
        String str1 = url.substring(rtsp_prefix.length());
        if (null== str1 || str1.isEmpty())
            return;
    
        int last_at_pos;
        int pos = str1.indexOf('/');
        if (pos > -1)
            last_at_pos = str1.lastIndexOf('@', pos);
        else
            last_at_pos = str1.lastIndexOf('@');
    
        String new_url = str1;
        String user_password = null;
        if (last_at_pos > -1) {
            new_url = str1.substring(last_at_pos+1);
            user_password = str1.substring(0, last_at_pos);
        }
    
        if (null == new_url || new_url.isEmpty())
            return;
    
        new_url = rtsp_prefix + new_url;
        String user = null, password = null;
    
        if (user_password != null && !user_password.isEmpty()) {
            int pos1 = user_password.indexOf(':');
            if (pos1 >-1) {
                user = user_password.substring(0, pos1);
                password = user_password.substring(pos1+1);
            }else
                user = user_password;
    
            if (user !=null && !user.isEmpty()) {
                try {
                    user = java.net.URLDecoder.decode(user, "UTF-8");
                }
                catch (Exception e){
                    Log.e(TAG, "Exception:", e);
                }
            }
    
            if (password != null && !password.isEmpty()) {
                try {
                    password = java.net.URLDecoder.decode(password, "UTF-8");
                }catch (Exception e) {
                    Log.e(TAG, "Exception:", e);
                }
            }
        }
    
        Log.i(TAG, "rtsp org_url:" + in_url + ", url:" + new_url + ", user:" + user + ", password:" + password);
    
        lib_player_.SmartPlayerSetUrl(get(), new_url);
        lib_player_.SetRTSPAuthenticationInfo(get(), user, password);
    }
    
    public void setSurfaceView(View surface_view) {
        this.surface_view_ = surface_view;
    }
    
    private void setPlayerParam(boolean is_hardware_decoder, boolean is_enable_hardware_render_mode, boolean is_mute)
    {
         Surface surface = null;
         int surface_codec_media_color_format = 0;
    
         if (surface_view_ != null && surface_view_ instanceof SurfaceView && ((SurfaceView) surface_view_).getHolder() != null)
             surface = ((SurfaceView) surface_view_).getHolder().getSurface();
    
         lib_player_.SetSurface(get(), surface, surface_codec_media_color_format, 0, 0);
    
        lib_player_.SmartPlayerSetRenderScaleMode(get(), 1);
    
        //int render_format = 1;
        //lib_player.SmartPlayerSetSurfaceRenderFormat(handle, render_format);
    
        //int is_enable_anti_alias = 1;
        //lib_player.SmartPlayerSetSurfaceAntiAlias(handle, is_enable_anti_alias);
    
        if (is_hardware_decoder && is_enable_hardware_render_mode) {
            lib_player_.SmartPlayerSetHWRenderMode(get(), 1);
        }
    
        lib_player_.SmartPlayerSetAudioOutputType(get(), 1);
    
        lib_player_.SmartPlayerSetMute(get(), is_mute ? 1 : 0);
    
        if (is_hardware_decoder) {
            int isSupportHevcHwDecoder = lib_player_.SetSmartPlayerVideoHevcHWDecoder(get(), 1);
    
            int isSupportH264HwDecoder = lib_player_.SetSmartPlayerVideoHWDecoder(get(), 1);
    
            Log.i(TAG, "isSupportH264HwDecoder: " + isSupportH264HwDecoder + ", isSupportHevcHwDecoder: " + isSupportHevcHwDecoder);
        }
    
        boolean isLowLatency = true;
        lib_player_.SmartPlayerSetLowLatencyMode(get(), isLowLatency ? 1 : 0);
    
        boolean is_flip_vertical = false;
        lib_player_.SmartPlayerSetFlipVertical(get(), is_flip_vertical ? 1 : 0);
    
        boolean is_flip_horizontal = false;
        lib_player_.SmartPlayerSetFlipHorizontal(get(), is_flip_horizontal ? 1 : 0);
    
        int rotate_degrees = 0;
        lib_player_.SmartPlayerSetRotation(get(), rotate_degrees);
    
        int curAudioVolume = 100;
        lib_player_.SmartPlayerSetAudioVolume(get(), curAudioVolume);
    }
    
    class EventHandleV2 implements NTSmartEventCallbackV2 {
        @Override
        public void onNTSmartEventCallbackV2(long handle, int id, long param1,
                                             long param2, String param3, String param4, Object param5) {
    
            if(event_listener_ != null)
            {
                event_listener_.onPlayerEventCallback(handle, id, param1, param2, param3, param4, param5);
            }
        }
    }
    
    public boolean setMute(boolean is_mute) {
        if (!check_native_handle())
            return false;
    
        return OK == lib_player_.SmartPlayerSetMute(get(), is_mute? 1 : 0);
    }
    
    public boolean setAudioVolume(int volume) {
        if (!check_native_handle())
            return false;
    
        return OK == lib_player_.SmartPlayerSetAudioVolume(get(), volume);
    }
    
    public boolean captureImage(int compress_format, int quality, String file_name, String user_data_string) {
        if (!check_native_handle())
            return false;
    
        return OK == lib_player_.CaptureImage(get(), compress_format, quality, file_name, user_data_string);
    }
    
    public boolean startPlayer(boolean is_hardware_decoder, boolean is_enable_hardware_render_mode, boolean is_mute) {
        if (is_playing()) {
            Log.e(TAG, "already playing, native_handle:" + get());
            return false;
        }
    
        setPlayerParam(is_hardware_decoder, is_enable_hardware_render_mode, is_mute);
    
        int ret = lib_player_.SmartPlayerStartPlay(get());
        if (ret != OK) {
            Log.e(TAG, "call StartPlay failed, native_handle:" + get() + ", ret:" + ret);
            return false;
        }
    
        write_lock_.lock();
        try {
            this.is_playing_ = true;
        } finally {
            write_lock_.unlock();
        }
    
        Log.i(TAG, "call StartPlayer OK, native_handle:" + get());
        return true;
    }
    
    public boolean stopPlayer() {
        if (!check_native_handle())
            return false;
    
        if (!is_playing()) {
            Log.w(TAG, "it's not playing, native_handle:" + get());
            return false;
        }
    
        boolean is_need_call = false;
        write_lock_.lock();
        try {
            if (this.is_playing_) {
                this.is_playing_ = false;
                is_need_call = true;
            }
        } finally {
            write_lock_.unlock();
        }
    
        if (is_need_call)
            lib_player_.SmartPlayerStopPlay(get());
    
        return true;
    }
    
    public boolean configRecorderParam(String rec_dir, int file_max_size, int is_transcode_aac,
                                       int is_record_video, int is_record_audio) {
    
        if(!check_native_handle())
            return false;
    
        if (null == rec_dir || rec_dir.isEmpty())
            return false;
    
        int ret = lib_player_.SmartPlayerCreateFileDirectory(rec_dir);
        if (ret != 0) {
            Log.e(TAG, "Create record dir failed, path:" + rec_dir);
            return false;
        }
    
        if (lib_player_.SmartPlayerSetRecorderDirectory(get(), rec_dir) != 0) {
            Log.e(TAG, "Set record dir failed , path:" + rec_dir);
            return false;
        }
    
        if (lib_player_.SmartPlayerSetRecorderFileMaxSize(get(),file_max_size) != 0) {
            Log.e(TAG, "SmartPlayerSetRecorderFileMaxSize failed.");
            return false;
        }
    
        lib_player_.SmartPlayerSetRecorderAudioTranscodeAAC(get(), is_transcode_aac);
    
        // 更细粒度控制录像的, 一般情况无需调用
        lib_player_.SmartPlayerSetRecorderVideo(get(), is_record_video);
        lib_player_.SmartPlayerSetRecorderAudio(get(), is_record_audio);
        return true;
    }
    
    public boolean startRecorder() {
    
        if (is_recording()) {
            Log.e(TAG, "already recording, native_handle:" + get());
            return false;
        }
    
        int ret = lib_player_.SmartPlayerStartRecorder(get());
        if (ret != OK) {
            Log.e(TAG, "call SmartPlayerStartRecorder failed, native_handle:" + get() + ", ret:" + ret);
            return false;
        }
    
        write_lock_.lock();
        try {
            this.is_recording_ = true;
        } finally {
            write_lock_.unlock();
        }
    
        Log.i(TAG, "call SmartPlayerStartRecorder OK, native_handle:" + get());
        return true;
    }
    
    public boolean stopRecorder() {
        if (!check_native_handle())
            return false;
    
        if (!is_recording()) {
            Log.w(TAG, "it's not recording, native_handle:" + get());
            return false;
        }
    
        boolean is_need_call = false;
        write_lock_.lock();
        try {
            if (this.is_recording_) {
                this.is_recording_ = false;
                is_need_call = true;
            }
        } finally {
            write_lock_.unlock();
        }
    
        if (is_need_call)
            lib_player_.SmartPlayerStopRecorder(get());
    
        return true;
    }
    
    public boolean switchPlaybackUrl(String url) {
        if (!check_native_handle()) return false;
        return OK == lib_player_.SmartPlayerSwitchPlaybackUrl(get(), url);
    }
    
    public boolean setRotation(int degrees) {
        if (!check_native_handle()) return false;
        return OK == lib_player_.SmartPlayerSetRotation(get(), degrees);
    }
    
    public boolean setFlipVertical(boolean flip) {
        if (!check_native_handle()) return false;
        return OK == lib_player_.SmartPlayerSetFlipVertical(get(), flip ? 1 : 0);
    }
    
    public boolean setFlipHorizontal(boolean flip) {
        if (!check_native_handle()) return false;
        return OK == lib_player_.SmartPlayerSetFlipHorizontal(get(), flip ? 1 : 0);
    }
    
    public boolean setLowLatencyMode(boolean enable) {
        if (!check_native_handle()) return false;
        return OK == lib_player_.SmartPlayerSetLowLatencyMode(get(), enable ? 1 : 0);
    }
    
    public boolean setFastStartup(boolean enable) {
        if (!check_native_handle()) return false;
        return OK == lib_player_.SmartPlayerSetFastStartup(get(), enable ? 1 : 0);
    }
    
    private static boolean is_null_or_empty(String val) {
        return null == val || val.isEmpty();
    }

    }

总结

大牛直播 SDK 提供了全面的 RTSP|RTMP 播放功能,包括低延迟播放、录像、截图等。通过合理配置参数和利用其提供的 API,开发者可以快速实现高效稳定的直播播放应用。在实际项目中,建议根据具体需求对播放器进行深度定制,以提升用户体验。无论是播放RTSP还是RTMP流,延迟均控制在100-300ms区间,满足平衡操控等对延迟要求苛刻的使用场景,以上抛砖引玉,感兴趣的开发者,可以单独跟我沟通探讨。

相关推荐
音视频牛哥10 分钟前
Linux平台实现低延迟的RTSP、RTMP播放
音视频开发·视频编码·直播
心走4 小时前
鸿蒙WebRTC编译指南&踩坑(Native 编译指导)
harmonyos·音视频开发
音视频牛哥1 天前
RTSP播放器实现回调RGB|YUV给视觉算法,然后二次编码推送到RTMP服务
音视频开发·视频编码·直播
音视频牛哥1 天前
Andorid平台实现高性能低延迟的多路RTSP播放器
音视频开发·视频编码·直播
声知视界2 天前
音视频基础能力之 Android 音频篇 (六):音频编解码到底哪家强?
android·音视频开发
深海丧鱼3 天前
什么!只靠前端实现视频分片? - 网络篇
前端·音视频开发
深海丧鱼4 天前
什么!只靠前端实现视频分片?
前端·音视频开发
心走4 天前
WebRTC系列 WebGL 绘制YUV 画面
前端·音视频开发
北有花开4 天前
Android音视频-Lame编译(Android 15 16K)对齐 和 addr2line使用
android·前端·音视频开发