前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Android平台RTSP|RTMP播放器技术实践:基于大牛直播SDK的深度探索

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

原创
作者头像
音视频牛哥
发布于 2025-04-25 05:10:11
发布于 2025-04-25 05:10:11
21100
代码可运行
举报
运行总次数:0
代码可运行

​前言

移动直播视频监控等场景中,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 中声明必要权限:
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
  • SO 库加载:在代码中通过 System.loadLibrary("SmartPlayer"); 加载原生库。

核心功能实现

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

功能支持

  • 音频:AAC/Speex(RTMP)/PCMA/PCMU;
  • 视频:H.264、H.265;
  • 播放协议:RTSP|RTMP;
  • 支持纯音频、纯视频、音视频播放;
  • 支持多实例播放;
  • 支持软解码,特定机型硬解码;
  • 支持RTSP TCPUDP模式设置;
  • 支持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及以上版本。

播放器初始化与播放控制

代码语言:java
AI代码解释
复制
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 接口,可以接收播放器状态更新等事件:

代码语言:java
AI代码解释
复制
    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);
            }
        }
    }

设置回调:

代码语言:java
AI代码解释
复制
libPlayer.SetSmartPlayerEventCallbackV2(playerHandle, new EventCallback());

录像功能集成

代码语言:java
AI代码解释
复制
@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);
			}
		}
	}
}

实时截图功能

代码语言:java
AI代码解释
复制
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);
	}
});

视频画面控制

代码语言:java
AI代码解释
复制
// 水平翻转
libPlayer.SmartPlayerSetFlipHorizontal(playerHandle, 1);

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

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

性能优化与最佳实践

  • 硬解码优先:在支持的设备上优先启用硬解码(H.264/H.265),可显著降低 CPU 负载。
  • 缓冲策略调整:根据网络条件动态调整缓冲区大小,平衡延迟与流畅度。
  • 多实例管理:通过封装播放器实例,支持多路视频同时播放。
代码语言:java
AI代码解释
复制
/*
 * 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;

    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区间,满足平衡操控等对延迟要求苛刻的使用场景,以上抛砖引玉,感兴趣的开发者,可以单独跟我沟通探讨。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
彻底理解 WireGuard 的路由策略
很久以前,我们只需要在 Linux 终端中输入 route -n(后来演变出了 ip route,也就是 iproute2 提供的命令),就可以知晓系统中所有数据包的走向,但是,大人,时代变了!
米开朗基杨
2022/11/07
7.5K0
彻底理解 WireGuard 的路由策略
☀️苏州程序大白解析Linux 中的虚拟网络接口☀️《❤️记得收藏❤️》
注意: 本文中使用 ip 命令创建或修改的任何网络配置,都是未持久化的,主机重启即消失。 ​
苏州程序大白
2021/10/20
2.3K0
Kubernetes网络模型
在Kubernetes中设计了一种网络模型,要求无论容器运行在集群中的哪个节点,所有容器都能通过一个扁平的网络平面进行通信,即在同一IP网络中。需要注意的是:在K8S集群中,IP地址分配是以Pod对象为单位,而非容器,同一Pod内的所有容器共享同一网络名称空间。
mikelLam
2022/10/31
1.2K0
Kubernetes网络模型
Linux|聊聊Linux系统中的路由策略
路由是沟通任何双边关系的基础,比如现实世界中的邮路,网络世界中的路由,都是用来连接任何需要联系的双方实体。
琉璃康康
2023/11/20
9120
Linux|聊聊Linux系统中的路由策略
Calico on Kubernetes
由于两台物理机的容器网段不同,我们完全可以将两台物理机配置成为路由器,并按照容器的网段配置路由表。
看、未来
2022/06/14
5610
Calico on Kubernetes
深入解析容器网络
在使用kubernetes之前, 最为火热的技术就是Docker技术了。 它完成了从虚拟机时代的过度,是走向云原生时代的开端。 但是由于docker的故步自封导致被google的开源的kubernetes后来居上, 现在的Docker虽然在积极改进, 但是主流大势已经被kubernetes掌握, 所以也只能起到助力作用。 回归正题, 在kubernetes的服务发现, 服务网格等技术火热之前, 是怎么来实现容器之间的通信呢 ?本文我们就来探讨一下。
用户11097514
2024/08/05
1760
深入解析容器网络
Kubernetes网络之Calico
Calico是Kubernetes生态系统中另一种流行的网络选择。虽然Flannel被公认为是最简单的选择,但Calico以其性能、灵活性而闻名。Calico的功能更为全面,不仅提供主机和pod之间的网络连接,还涉及网络安全和管理。Calico CNI插件在CNI框架内封装了Calico的功能。
仙人技术
2020/06/27
11.5K0
Kubernetes网络之Calico
天天讲路由,那 Linux 路由到底咋实现的!?
容器是一种新的虚拟化技术,每一个容器都是一个逻辑上独立的网络环境。Linux 上提供了软件虚拟出来的二层交换机 Bridge 可以解决同一个宿主机上多个容器之间互连的问题,但这是不够的。二层交换无法解决容器和宿主机外部网络的互通。
开发内功修炼
2022/03/24
3.1K0
天天讲路由,那 Linux 路由到底咋实现的!?
docker网络
我们在使用 docker run 创建 Docker 容器时,可以用--net 选项指定容器的网络模式,Docker 有以下 4 种网络模式:
cuijianzhe
2022/06/14
6690
docker网络
LINUX主机的策略路由配置,多网卡一样可以指哪走哪
因《网工必备技能!Windows网卡1访问外网,网卡2访问内网!》受到粉丝的关注,就有粉丝提出,想给一台拥有多个网卡的linux主机,想配置不同的网卡走不通的路由,怎么操作呢?阿祥今天就介绍这种需求的配置方法,希望对粉丝有帮助!
ICT系统集成阿祥
2024/12/03
5330
LINUX主机的策略路由配置,多网卡一样可以指哪走哪
Docker实践之09-高级网络配置
当Docker启动时,会自动在主机上创建一个名为docker0虚拟网桥,实际上是Linux的一个bridge,可以理解为一个软件交换机,它会在挂载到它的网口之间进行转发。 同时,Docker随机分配一个本地未占用的私有网段中的一个地址给docker0接口。比如典型的172.17.0.1,掩码为255.255.0.0。此后启动的容器内的网口也会自动分配一个同一网段(172.17.0.0/16)的地址。 当创建一个Docker容器的时候,同时会创建了一对veth pair接口(当数据包发送到一个接口时,另外一个接口也可以收到相同的数据包)。这对接口一端在容器内,即一端在本地并被挂载到docker0网桥,名称以veth开头(例如vethb305ad8)。通过这种方式,主机可以跟容器通信,容器之间也可以相互通信。 Docker创建了在主机和所有容器之间一个虚拟共享网络。
编程随笔
2022/09/16
1.3K0
Docker实践之09-高级网络配置
Docker 容器如何访问外部网络以及端口映射原理?
不必太纠结于当下,也不必太忧虑未来,当你经历过一些事情的时候,眼前的风景已经和从前不一样了。——村上春树
山河已无恙
2024/04/11
3.5K0
Docker 容器如何访问外部网络以及端口映射原理?
Linux-网络设置
ubuntu18 以上/etc/network/interfaces 已经不再生效,改用netplan
孔西皮
2023/10/18
2700
kubernetes(十八)集群网路
OSI(Open System Interconnection)是国际标准化组织(ISO)制定的一个用于计算机或通信系统间互联的标准体系,一般称为OSI参考模型或七层模型。
alexhuiwang
2020/09/23
1.5K0
kubernetes(十八)集群网路
为了能工作到65岁不被淘汰,努力学会了公有LINUX云主机的策略路由配置!
近期有粉丝留言说:买了公有云linux云主机,想配置双网卡通公网玩玩,但不知道怎么配置,那么这期我们就介绍这个场景的操作配置,希望对更多的人有帮助!
ICT系统集成阿祥
2024/12/03
1090
为了能工作到65岁不被淘汰,努力学会了公有LINUX云主机的策略路由配置!
Kubernetes 中数据包的生命周期 -- 第 2 部分
正如我们在第 1 部分中所讨论的,CNI 插件在 Kubernetes 网络中起着至关重要的作用。当前有许多第三方 CNI 插件可供使用,Calico 便是其中之一。凭借着良好的易用性以及对多种网络架构的支持,Calico 获得了许多工程师的青睐。
Se7en258
2022/06/24
9940
Kubernetes 中数据包的生命周期 -- 第 2 部分
Linux下路由配置梳理
在日常运维作业中,经常会碰到路由表的操作。下面就linux运维中的路由操作做一梳理: ------------------------------------------------------------------------------ 先说一些关于路由的基础知识: 1)路由概念 路由:   跨越从源主机到目标主机的一个互联网络来转发数据包的过程 路由器:能够将数据包转发到正确的目的地,并在转发过程中选择最佳路径的设备 路由表:在路由器中维护的路由条目,路由器根据路由表做路径选择 直连路由:当在路由器
洗尽了浮华
2018/01/23
7.3K0
Linux下路由配置梳理
[译]数据包在 Kubernetes 中的一生(2)
如前文所述,CNI 插件是 Kubernetes 网络的重要组件。目前有很多第三方 CNI 插件,Calico 就是其中之一,因为它的易用性和网络能力,得到很多工程师的青睐。它支持很多不同的平台,例如 Kubernetes、OpenShift、Docker EE、OpenStack 以及裸金属服务。Calico Node 组件以 Docker 容器的形式运行在 Kubernetes 的所有 Master 和 Node 节点上。Calico-CNI 插件会直接集成到 Kubernetes 每个节点的 Kubelet 进程中,一旦发现了新建的 Pod,就会将其加入 Calico 网络。
CNCF
2021/07/07
9470
外包精通--k8s之flannel网络
vxlan(virtual Extensible LAN)虚拟可扩展局域网,是一种overlay的网络技术,使用MAC in UDP的方法进
Godev
2023/06/25
9740
使用 Linux 网络虚拟化技术探究容器网络原理
在 使用 Go 和 Linux Kernel 技术探究容器化原理 一文中,我们揭秘了容器的本质就是一个特殊的进程,特殊在为其创建了 NameSpace 隔离运行环境,并用 Cgroups 为其控制资源开销。
gopher云原生
2022/11/22
1.7K0
使用 Linux 网络虚拟化技术探究容器网络原理
推荐阅读
相关推荐
彻底理解 WireGuard 的路由策略
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验