LiteAVSDK API文档 http://imgcache.qq.com/open/qcloud/video/act/liteav_android_doc/index.html


Android 端常见问题(FAQ)


怎么确保在推流或者播放过程中不会灭屏?

发布时间:2018年6月15日 更新时间:2018年6月15日 贡献者:yyuanchen

在 Activity 的 onCreate() 方法中,增加以下两行代码即可。注意这两行代码一定要放在 setContentView() 之前

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    // 保证不灭屏
    getWindow().addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
    getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);

    setContentView(R.layout.activity_live_publidher);
}

SDK 是否支持推流到其他云厂商或海外推拉流 ?

发布时间:2018年6月15日 更新时间:2018年6月15日 贡献者:yyuanchen

我们 SDK 没有对域名做限制,所以支持推流到其他云厂商。另外,我们腾讯云在世界各地都有相关节点,能满足您海外推拉流的需求。 注意:如果你有以上需求, 需要在代码中将智能选路关闭

mLivePushConfig.enableNearestIP(false);
mLivePusher.setConfig(mLivePushConfig);

如何打印移动直播 SDK 相关的 Log 日志?

发布时间:2018年6月19日 更新时间:2018年6月19日 贡献者:yyuanchen

SDK 提供了设置 log 是否在控制台打印以及 log 的级别的接口,具体代码如下:

// 建议在 Application 类中调用
// 设置是否在 Android Studio 的控制台打印 SDK 的相关输出。
TXLiveBase.setConsoleEnabled(true);
// 设置是否允许 SDK 打印本地 log,SDK 默认会将 log 写到 sdcard 上的 log / tencent / liteav 文件夹下。
TXLiveBase.setLogLevel(TXLiveConstants.LOG_LEVEL_DEBUG);

直播播放场景如何处理停止拉流和恢复播放的操作?

发布时间:2018年6月19日 更新时间:2018年6月19日 贡献者:yyuanchen

在直播场景下,如果您希望播放器进入后台停止播放,恢复前台继续播放。您可以参考以下代码进行设置。

@Override
public void onStop(){
    super.onStop();

    if (mLivePlayer != null) {
        mLivePlayer.pause();
    }
}

@Override
public void onResume() {
    super.onResume();

    if (mLivePlayer != null) {
        mLivePlayer.resume();
    }
}

点播播放场景如何处理停止播放和恢复播放的操作?

发布时间:2018年6月19日 更新时间:2018年6月19日 贡献者:yyuanchen

在点播场景下,如果您希望播放器进入后台停止播放,恢复前台继续播放。您可以参考以下代码进行设置。

@Override
public void onStop(){
    super.onStop();

    if (mVodPlayer != null) {
        mVodPlayer.pause();
    }
}

@Override
public void onResume() {
    super.onResume();

    if (mVodPlayer != null) {
        mVodPlayer.resume();
    }
}

SDK 对硬编支持情况如何?

发布时间:2018年6月19日 更新时间:2018年6月19日 贡献者:yyuanchen

  • 兼容性评估

Android 手机目前对硬件加速的支持已较前两年有明显的进步,目前支持度还是不错的,但仍有个别机型有兼容性问题,目前移动直播 SDK 有通过一个内部的黑名单进行控制,避免在部分兼容性差的机型上出现问题。

  • 效果差异

开启硬件加速后手机耗电量会有明显降低,机身温度也会比较理想,但画面大幅运动时马赛克感会比软编码要明显很多,而且越是早起的低端机,马赛克越是严重。所以如果您是对画质要求很高的客户,不推荐开启硬件加速。我们通过测试发现,推流的分辨率设置 360P 或 540P,选择软编即可,不需要使用硬件加速。

  • 推荐配置

如果您不清楚要何时开启硬件加速, 建议设置为 ENCODE_VIDEO_AUTO

// ENCODE_VIDEO_AUTO 默认是启用软件编码, 但手机 CPU 使用率超过 80% 或者帧率 <= 10, SDK 内部会自动切换为硬件编码。
if (mHWVideoEncode){
    if (mLivePushConfig != null) {
        if(Build.VERSION.SDK_INT < 18){
            Toast.makeText(getApplicationContext(), "硬件加速失败,当前手机 API 级别过低(最低 18)", 
                Toast.LENGTH_SHORT).show();
            mHWVideoEncode = false;
        }
    }
}

mLivePushConfig.setHardwareAcceleration(mHWVideoEncode ? 
    TXLiveConstants.ENCODE_VIDEO_HARDWARE : TXLiveConstants.ENCODE_VIDEO_SOFTWARE);
mLivePusher.setConfig(mLivePushConfig);

SDK 播放器支持硬件加速吗?

发布时间:2018年7月7日 更新时间:2018年7月7日 贡献者:yyuanchen

直播播放器(TXLivePlayer) 和 点播播放器(TXVodPlayer)都是支持的。具体如何开启硬件加速,参考以下代码:

// 硬件加速在1080p解码场景下效果显著,但只有 4.3 以上android系统才支持
// enableHardwareDecode 表示是否启动硬件加速
// true:启用视频硬解码. false:禁用视频硬解码. 启用默认的视频软解码.
// 直播播放器启动硬解
mLivePlayer.enableHardwareDecode(true); 
// 点播播放器启动硬解
mVodPlayer.enableHardwareDecode(true);

推荐场景下,TXLivePusher 是否支持切屏推流?

发布时间:2018年6月19日 更新时间:2018年6月19日 贡献者:yyuanchen

支持的。我们推流( TXLivePusher )支持以下场景:

  • 摄像机预览画面在两个 Activity 之间来回切换。 要求:每个 Activity 的布局中必须包含有一个 TXCloudVideoView

  • 摄像机预览画面在 Activity 与 悬浮窗之间来回切换。 要求:Activity 和悬浮窗布局中必须包含有一个 TXCloudVideoView

我们以预览画面在 Activity 之间切换做例子进行说明,其他场景类似:

/* 建议将 TXLivePusher 做成单例 */
// FirstPlayActivity
private TXCloudVideoView first_captureView;
mLivePusher.startCameraPreview(first_captureView);
mLivePusher.setVideoQuality(TXLiveConstants.VIDEO_RESOLUTION_TYPE_540_960, false, false);
mLivePusher.startPusher(rtmpUrl);

// SecondPlayActivity
private TXCloudVideoView second_captureView;
// 必须先调用 stopCameraPreview(), 然后才能调用 startCameraPreview()
mLivePusher.stopCameraPreview(true);
mLivePusher.startCameraPreview(second_captureView);
/* 建议将 TXLivePusher 做成单例 */
// FirstPlayActivity
private TXCloudVideoView first_captureView;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    mLivePusher.startCameraPreview(first_captureView);
    mLivePusher.setVideoQuality(TXLiveConstants.VIDEO_RESOLUTION_TYPE_540_960, false, false);
    mLivePusher.startPusher(rtmpUrl);
}

@Override
protected void onStop() {
    super.onStop();
    // 通知 SDK 进入“后台推流模式”了
    mLivePusher.pausePusher(); 
}


// SecondPlayActivity
private TXCloudVideoView second_captureView;

@Override
protected void onResume() {
    super.onResume();
    // 通知 SDK 重回前台推流
    mLivePusher.pausePusher(); 
}

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    // 必须先调用 stopCameraPreview(), 然后才能调用 startCameraPreview()
    mLivePusher.stopCameraPreview(true);
    mLivePusher.startCameraPreview(second_captureView);
}

播放(直播或者点播)场景下,播放器是否支持切屏播放?

发布时间:2018年6月19日 更新时间:2018年6月19日 贡献者:yyuanchen

支持的。我们播放器支持以下场景:

  • 视频画面在两个 Activity 之间来回切换。 要求:每个 Activity 的布局中必须包含有一个 TXCloudVideoView

  • 视频画面在 Activity 与 悬浮窗之间来回切换。 要求:Activity 和悬浮窗布局中必须包含有一个 TXCloudVideoView

我们以视频画面在 Activity 之间切换做例子进行说明,其他场景类似:

/* 建议将播放器做成单例 */
// FirstPlayActivity
private TXCloudVideoView first_videoView;
mTXLiveplayer.setPlayerView(first_videoView);
mTXLiveplayer.startPlay(playUrl, TXLivePlayer.PLAY_TYPE_LIVE_FLV);


// SecondPlayActivity
private TXCloudVideoView second_videoView;
mTXLiveplayer.setPlayerView(second_videoView);
// 必须先调用 stopPlay(), 然后才能调用 startPlay()
mTXLiveplayer.stopPlay(false);
mTXLiveplayer.startPlay(playUrl, TXLivePlayer.PLAY_TYPE_LIVE_FLV);

如何减少 APK 体积?

发布时间:2018年6月23日 更新时间:2018年6月23日 贡献者:yyuanchen

整个 SDK 的体积主要来自于 so 文件,这些 so 文件是 SDK 正常运行所依赖的音视频编解码库、图像处理库 以及 声学处理组件。各个 so 库的具体作用,可以阅读 库说明

如果您对 APK 体积大小有要求的话,您可以考虑采用在线加载的方式减少最终 apk 安装包的大小。具体改造如下:

  1. 使用 jar + so 方式集成 到官网下载 SDK,解压 LiteAVSDK_xxx.zip 压缩包后得到 libs 目录,里面主要包含 so 文件和 jar 文件。
  2. 上传 SO 文件 将 SDK 压缩包中的 so 文件上传到 CDN,并记录下载地址,比如 http://xxx.com/so_files.zip。
  3. 启动准备 在用户启动 SDK 相关功能前,比如开始播放视频之前,先用 loading 动画提示用户“正在加载相关的功能模块”。
  4. 下载 SO 文件 在用户等待过程中,APP 就可以到 http://xxx.com/so_files.zip 下载 so 文件,并存入应用目录下(比如应用根目录下的 files 文件夹),为了确保这个过程不受运营商 DNS 拦截的影响,请在文件下载完成后校验 so 文件的完整性。
  5. 加载 SO 文件 等待所有 so 文件就位以后,调用 TXLiveBase 的 setLibraryPath 将下载的目标 path 设置给 SDK, 然后再调用 SDK 的相关功能。之后,SDK 会到这些路径下加载需要的 so 文件并启动相关功能。

推流端是否有重连机制?

发布时间:2018年6月23日 更新时间:2018年6月23日 贡献者:yyuanchen

在推流过程中,主播难免会遇到网络波动的情况。这种情况可能会造成推流通信链路的链接断开,从而影响主播正常直播。从主播的体验角度上看,主播一般会希望网络恢复的时候,自己无须执行其他操作就能继续直播。

我们 SDK 推流端提供了重连机制,你可以在 TXLivePusherConfig进行设置。

TXLivePushConfig mLivePushConfig = new TXLivePushConfig();
// 设置推流端重连次数, 默认值为3
mLivePushConfig.setConnectRetryCount(3);
// 设置推流端重连间隔, 单位秒, 默认值为3
mLivePushConfig.setConnectRetryInterval(3);
mLivePusher.setConfig(mLivePushConfig);

这两个 API 接口的详细用法,见 API 接口说明

如果三次重连都失败的话,SDK 会通过 TXLivePushListener 代理来回调 -1307(PUSH_ERR_NET_DISCONNECT )的事件状态码。这时应该提示主播网络连接断开了,建议换个好的网络环境,再进行直播。


播放端是否有重连机制?

发布时间:2018年7月7日 更新时间:2018年7月7日 贡献者:yyuanchen

主播可能会因为网络问题导致断流重连,重连的时间可能短。为了让观众有更好的观看直播的体验。即主播发生断流重连的情况,观众无需执行任何操作,静静等待主播恢复直播即可。

我们 SDK 播放端提供了重连机制,你可以在 TXLivePlayConfig进行设置。

TXLivePlayConfig mPlayConfig= new TXLivePlayConfig();
// 设置播放器重连次数, 默认值为3
mPlayConfig.setConnectRetryCount(3);
// 设置播放器重连间隔, 单位秒, 默认值为3
mPlayConfig.setConnectRetryInterval(3);
mLivePlayer.setConfig(mPlayConfig);

这两个 API 接口的详细用法,见 API 接口说明

如果三次重连都失败的话,SDK 会通过 TXLivePlayListener 代理来回调 -2301(PLAY_ERR_NET_DISCONNECT)的事件状态码。这是说明主播重连不上去,可以提醒观众已经下播了。


对比推流端的预览画面和播放端的画面,为什么是左右颠倒?

发布时间:2018年7月7日 更新时间:2018年7月7日 贡献者:yyuanchen

主播进行直播一般选择前摄像头。前摄像头采集到的画面跟我们镜子中的画面是一样的。我们平时照镜子,会发现镜子中的画面跟现实的画面是左右颠倒的。如果您希望推流端的预览画面和播放端的画面一致,您可以调用镜像接口。

 mLivePusher.setConfig(mLivePushConfig);
 // 建议在 setConfig 后面调用 setMirror() 接口
 // 一般前摄像头才需要调用,后摄像头不需要调用
 // true:做镜像 false:不做镜像
 mLivePusher.setMirror(true);

SDK 是否支持发送自定义采集的视频数据?

发布时间:2018年7月7日 更新时间:2018年7月7日 贡献者:yyuanchen

支持的。我们移动直播 SDK 提供了 sendCustomVideoData(buffer, bufferType, w, h) 接口。该接口是向 SDK 塞入您自定义采集和处理后的视频数据(美颜、滤镜等),目前只支持 i420 格式。该接口适用场景是只想使用我们移动直播 SDK 来编码和推流。调用该接口前提是,不再调用 TXLivePusher 的 startCameraPreview 接口。

接口详情:int sendCustomVideoData(byte[] buffer, int bufferType, int w, int h)

  • 参数说明:
参数 类型 说明
buffer byte[] 视频数据
bufferType int 视频格式.目前只支持 TXLivePusher.YUV_420P和TXLivePusher.RGB_RGBA
w int 视频图像的宽度
h int 视频图像的高度
  • 返回结果的说明:
结果 说明
大于或等于 0 发送成功,但帧率过高,超过了TXLivePushConfig中设置的帧率,帧率过高会导致视频编码器输出的码率超过TXLivePushConfig中设置的码率,返回值表示当前YUV视频帧提前的毫秒数
0 发送成功
-1 视频分辨率非法
-2 YUV数据长度与设置的视频分辨率所要求的长度不一致
-3 视频格式非法
-4 视频图像长宽不符合要求,画面比要求的小了
-1000 SDK内部错误

具体实例示例代码 :

//以下是简单的实例,获取摄像机预览回调的视频数据并推流
@Override
public void onPreviewFrame(byte[] data, Camera camera) {
    // 假设摄像机获取的视频格式是 NV21, 预览画面大小为 1280X720
    if (!isPush) {
    } else {
        // 开始自定义推流
        // 需要将视频格式转码为 I420
        byte[] buffer = new byte[data.length];
        buffer = nv21ToI420(data, mPreviewWidth, mPreviewHeight);

        int customModeType = 0;
        customModeType |= TXLiveConstants.CUSTOM_MODE_VIDEO_CAPTURE;
        // 只能分辨率的宽和高小于或者等于预览画面的宽和高的分辨率 
        // 还能选择 480x640 等,但不能选择 540x960。因指定分辨率的高(960) > 预览画面的高(720),编码器无法裁剪画面。
        mLivePushConfig.setVideoResolution(TXLiveConstants.VIDEO_RESOLUTION_TYPE_1280_720);
        mLivePushConfig.setAutoAdjustBitrate(false);
        mLivePushConfig.setVideoBitrate(1200);
        mLivePushConfig.setVideoEncodeGop(3);
        mLivePushConfig.setVideoFPS(15);
        mLivePushConfig.setCustomModeType(customModeType);
        mLivePusher.setConfig(mLivePushConfig);

        int result= mLivePusher.sendCustomVideoData(buffer, TXLivePusher.YUV_420P, mPreviewWidth, mPreviewHeight);
    }
}

/**
 * nv21转I420
 * @param data
 * @param width
 * @param height
 * @return
 */
public static byte[] nv21ToI420(byte[] data, int width, int height) {
    byte[] ret = new byte[data.length];
    int total = width * height;

    ByteBuffer bufferY = ByteBuffer.wrap(ret, 0, total);
    ByteBuffer bufferU = ByteBuffer.wrap(ret, total, total / 4);
    ByteBuffer bufferV = ByteBuffer.wrap(ret, total + total / 4, total / 4);

    bufferY.put(data, 0, total);
    for (int i=total; i<data.length; i+=2) {
        bufferV.put(data[i]);
        bufferU.put(data[i+1]);
    }
    return ret;
}

SDK 是否支持发送自定义采集的音频数据?

发布时间:2018年7月7日 更新时间:2018年7月7日 贡献者:yyuanchen

支持的。我们移动直播 SDK 提供了 sendCustomPCMData(byte[] pcmBuffer) 接口。该接口是向 SDK 塞入您自定义采集和处理后的音频数据,请使用单声道或双声道、16位宽、48000Hz 的 PCM 声音数据。如果是单声道,请保证传入的PCM长度为2048;如果是双声道,请保证传入的PCM长度为4096。该接口一般结合 sendCustomVideoData(buffer, bufferType, w, h) 一起使用

接口详情:void sendCustomPCMData(byte[] pcmBuffer)

  • 参数说明:
参数 类型 说明
pcmBuffer byte[] pcm 音频数据

具体实例示例代码 :

//以下是简单的实例,获取麦克风采集音频数据。
AudioRecord mAudioRecord = null;
int mMinBufferSize = 0; //最小缓冲区大小

private static final int DEFAULT_SOURCE = MediaRecorder.AudioSource.MIC;
// 设置采样率为 48000
private static final int DEFAULT_SAMPLE_RATE = 48000;
// 支持单声道(CHANNEL_IN_MONO) 和 双声道(CHANNEL_IN_STEREO)
private static final int DEFAULT_CHANNEL_CONFIG = AudioFormat.CHANNEL_IN_STEREO;  
// 量化位数
private static final int DEFAULT_AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT;    

private boolean mIsCaptureStarted = false;
private volatile boolean mIsLoopExit = true;

private Thread mCaptureThread;


@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    // 启动音频采集
    startCapture();
}


public boolean isCaptureStarted() {
    return mIsCaptureStarted;
}


public boolean startCapture() {
    return startCapture(DEFAULT_SOURCE, DEFAULT_SAMPLE_RATE, DEFAULT_CHANNEL_CONFIG,
            DEFAULT_AUDIO_FORMAT);
}

public boolean startCapture(int audioSource, int sampleRateInHz, int channelConfig, int audioFormat) {

    if (mIsCaptureStarted) {
        Log.e(TAG, "audio Capture already started !");
        return false;
    }

    // SDK 要求双声道要 4096, 单声道 2048
    mMinBufferSize = 4096;
    if (mMinBufferSize == AudioRecord.ERROR_BAD_VALUE) {
        Log.e(TAG, "Invalid AudioRecord parameter !");
        return false;
    }
    Log.d(TAG , "getMinBufferSize = "+mMinBufferSize+" bytes !");

    mAudioRecord = new AudioRecord(audioSource,sampleRateInHz,channelConfig,audioFormat,mMinBufferSize);
    if (mAudioRecord.getState() == AudioRecord.STATE_UNINITIALIZED) {
        Log.e(TAG, "AudioRecord initialize fail !");
        return false;
    }

    mAudioRecord.startRecording();

    mIsLoopExit = false;
    mCaptureThread = new Thread(new AudioCaptureRunnable());
    mCaptureThread.start();

    mIsCaptureStarted = true;
    Log.d(TAG, "Start audio capture success !");

    return true;
}

public void stopCapture() {
    if (!mIsCaptureStarted) {
        return;
    }

    mIsLoopExit = true;
    try {
        mCaptureThread.interrupt();
        mCaptureThread.join(1000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }

    if (mAudioRecord.getRecordingState() == AudioRecord.RECORDSTATE_RECORDING) {
        mAudioRecord.stop();
    }

    mAudioRecord.release();

    mIsCaptureStarted = false;

    Log.d(TAG, "Stop audio capture success !");
}

private class AudioCaptureRunnable implements Runnable {
    @Override
    public void run() {
        while (!mIsLoopExit) {

            byte[] buffer = new byte[mMinBufferSize];

            int ret = mAudioRecord.read(buffer, 0, mMinBufferSize);
            if (ret == AudioRecord.ERROR_INVALID_OPERATION) {
                Log.e(TAG , "AudioRecord Error ERROR_INVALID_OPERATION");
            } else if (ret == AudioRecord.ERROR_BAD_VALUE) {
                Log.e(TAG , "AudioRecord Error ERROR_BAD_VALUE");
            } else {

                if (isPush) {
                    mLivePusher.sendCustomPCMData(buffer);
                }
            }
            SystemClock.sleep(10);
        }
    }
}

results matching ""

    No results matching ""