直播功能

Android版本设备端LinkVisual SDK提供直播功能,本文介绍实现直播功能的过程。

下文简称设备端Android版本LinkVisual SDK为LinkVisual SDK。

前提条件

  • 已创建产品和设备,具体操作,请参见设备接入。

  • 已获取LinkVisual SDK,具体操作,请参见获取SDK

  • 已完成初始化LinkVisual SDK,具体操作,请参见初始化SDK

背景信息

直播功能通过RTMP协议推流。其支持的视频编码格式和音频编码格式如下:

  • 视频编码格式:H264和H265。

  • 音频编码格式:G711a、G711u和AAC_LC。

操作步骤

步骤一:注册直播事件监听器和流错误监听器。

LinkVisual SDK收到服务端下发的开始推流指令后,通过已注册的直播流事件监听器OnLiveStreamListener通知何时开始推流、结束推流或下发强制I帧等指令。

详细开发流程如下:

  1. 设置直播流事件监听。

    // 设置直播流事件监听
    IPCDev.getInstance().getIpcStreamManager().setOnLiveStreamListener(MainActivity.this);
    // 设置流错误监听
    IPCDev.getInstance().getIpcStreamManager().setOnStreamErrorListener(MainActivity.this);
  2. 接收服务端发送的开始推直播流请求。

    public interface OnLiveStreamListener{
        /**
         * 收到开始推直播流请求
         *
         * @param streamId 流ID
         * @param streamType 码流类型:0为主码流,1为辅码流
         * @param preTimeInS 预先录制时间,单位秒
         */
        void onStartPushLiveStreaming(final int streamId, final int streamType, final int preTimeInS);
    
        /**
         * 收到停止推流请求
         *
         * @param streamId 流ID
         */
        void onStopPushStreaming(final int streamId);
    
        /**
         * 收到强制I帧请求
         * 需立即构造一个I帧并发送
         * @param streamId 流ID
         */
        void onForceIFrame(int streamId);
    }

步骤二:处理开始直播推流请求。

  1. 当服务端下发推流请求时:

    1. 通过回调OnLiveStreamListener.onStartPushLiveStreaming(int streamId, int streamType, int preTimeInS)方法,通知设备端需要开始采流并推流。

    2. 处理开始直播推流请求时,一般需要同时开启IPC设备和录音机进行采流。

      • 若开启,请调用MediaCodec方法对IPC设备采集的数据进行H264编码,对录音机采集的数据进行音频编码,并设置对应格式的音视频参数。

      • 若不开启,请跳过此步骤。

        @Override
            public void onStartPushLiveStreaming(int streamId, int streamType, int preTimeInS){
                this.streamId = streamId;
                try {
        
                    // 构造视频参数
                    VideoStreamParams videoStreamParams = new VideoStreamParams();
                    // 直播流该参数始终为0
                    videoStreamParams.setDurationInS(0); 
                    videoStreamParams.setVideoFormat(VideoStreamParams.VIDEO_FORMAT_H264);
        
                    // 构造音频参数
                    AudioStreamParams audioStreamParams = new AudioStreamParams();
                    audioStreamParams.setAudioChannel(AudioStreamParams.AUDIO_CHANNEL_MONO);
                    audioStreamParams.setAudioFormat(AudioStreamParams.AUDIO_FORMAT_G711A);
                    audioStreamParams.setAudioEncoding(AudioStreamParams.AUDIO_ENCODING_16BIT);
                    audioStreamParams.setAudioSampleRate(AudioStreamParams.AUDIO_SAMPLE_RATE_8000);
        
                    // 设置推流参数
                    IPCDev.getInstance().getIpcStreamManager().setStreamParams(streamId, videoStreamParams, audioStreamParams);
        
                    // TODO 开始采流、编码并发送音视频数据
                } catch (NoSuchStreamException e) {
                    e.printStackTrace();
                }
            }
  2. 调用IPCStreamManager方法发送音视频数据。

         /**
         * 发送音频帧数据
         *
         * @param streamId 流ID
         * @param directByteBuffer 源数据
         * @param length 数据长度
         * @param timeStampInMs  音频帧时间戳,单位为毫秒
         */
         void sendAudioData(int streamId, ByteBuffer directByteBuffer, int length, long timeStampInMs) throws NoSuchStreamException
        /**
         * 发送视频帧数据
         *
         * @param streamId 流ID
         * @param directByteBuffer 源数据
         * @param length  数据长度
         * @param isIFrame  是否为I帧
         * @param timeStampInMs 视频帧时间戳,单位为毫秒
         */
        void sendVideoData(int streamId, ByteBuffer directByteBuffer, int length, boolean isIFrame, long timeStampInMs) throws NoSuchStreamException
    
         /**
         * 发送音频帧数据
         *
         * @param streamId 流ID
         * @param data 源数据
         * @param offset 偏移量
         * @param length 数据长度
         * @param timeStampInMs 音频帧时间戳,单位为毫秒
         * @deprecated 使用 {@link #sendAudioData(int, ByteBuffer, int, long)}来替换
         */
        @Deprecated
        void sendAudioData(int streamId, byte[] data, int offset, int length, long timeStampInMs) throws NoSuchStreamException
    
        /**
         * 发送视频帧数据
         *
         * @param streamId  流ID
         * @param data 源数据
         * @param offset  偏移量
         * @param length  数据长度
         * @param isIFrame   是否为I帧
         * @param timeStampInMs 视频帧时间戳,单位为毫秒
         * @deprecated 使用 {@link #sendVideoData(int, ByteBuffer, int, boolean, long)}来替换
         */
        @Deprecated
        public void sendVideoData(int streamId, byte[] data, int offset, int length, boolean isIFrame, long timeStampInMs) throws NoSuchStreamException
  3. 打印I帧的前256个字节,并查看结果。

    视频播放时对H264和H265的帧结构有如下要求,您可参考下面的代码,打印I帧的前256个字节查看帧结构。

    for (int i = 0; i < ((buffer_size > 256)?256:buffer_size); i++) {
        printf("%02x ", buffer[i]);
        if ((i + 1) % 30 == 0) {
            printf("\n");
        }
    }
    printf("\n");
    • H264的I帧格式要求为帧分隔符+SPS+帧分隔符+PPS+帧分隔符+IDR

      以下图为例,帧分隔符为0x000001或者0x00000001;序列参数集SPS(Sequence Parameter Set)起始数据为0x67;图像参数集PPS(Picture Parameter Set)起始数据为0x68;即时解码器刷新IDR(Instantaneous Decoding Refresh)起始数据为0x65。H264

    • H265的I帧格式要求为帧分隔符+VPS+帧分隔符+SPS+帧分隔符+PPS+帧分隔符+IDR

      以下图为例,帧分隔符为0x000001或者0x00000001;视频参数集VPS(Video Parameter Set)起始数据为0x40;SPS起始数据为0x42;PPS起始数据为0x44;IDR起始数据为0x26。H265

步骤三:处理结束推流请求。

服务端下发停止推流请求时:

  1. 回调OnLiveStreamListener.onStopPushLiveStreaming()方法通知设备端停止推流。

  2. 处理停止推流时,一般情况下需要同时停止采集IPC设备数据和录音机数据。

    • 若需要,请调用IPCStreamManager的stopStreaming(int streamId)方法实现。

    • 若不需要,请跳过此步骤。

/**
     * 收到停止推流请求
     *
     * @param streamId 流ID
     */
    @Override
    public void onStopPushStreaming(int streamId){
        // TODO 停止音视频数据的发送
        try {
            // 调用停止推流接口
            IPCDev.getInstance().getIpcStreamManager().stopStreaming(streamId);
        } catch (NoSuchStreamException e) {
            e.printStackTrace();
        }
    }

步骤四:处理流错误。

推流过程使用OnStreamErrorListener.onError(int streamId, StreamError error)方法接收和处理流错误。错误码详细信息,请参考本文下方的错误码

public interface OnStreamErrorListener{
    /**
     * 流异常时回调
     * @param streamId
     * @param error 参考StreamError定义
     */
    void onError(int streamId, StreamError error);
}

错误码

流错误码

错误码

标志符

描述

解决方法

1

StreamError.ERROR_STREAM_CREATE_FAILED

创建流实例失败。

该错误通常由系统资源不足引起,请您申请内存后重试。

2

StreamError.ERROR_STREAM_START_FAILED

建立RTMP链接失败。

请检查网络是否正常然后重试。

3

StreamError.ERROR_STREAM_STOP_FAILED

停止流失败。

因引入了无效的StreamId而引发的错误,该错误可忽略。

4

StreamError.ERROR_STREAM_SEND_VIDEO_FAILED

发送视频数据失败。

请根据RTMP错误码判断具体的出错原因。RTMP错误码的详细信息,请参考本文下方的RTMP错误码

5

StreamError.ERROR_STREAM_SEND_AUDIO_FAILED

发送音频数据失败。

请根据RTMP错误码判断具体的出错原因。RTMP错误码的详细信息,请参考本文下方的RTMP错误码

6

StreamError.ERROR_STREAM_INVALID_PARAMS

无效的流参数。

setStreamParams接口设置的参数无效,请检查并修改后重试。

RTMP错误码

说明
RTMP错误码只出现在日志中,仅用于排查详细问题。

错误码

标志符

描述

解决方法

-1

RTMP_ILLEGAL_INPUT

输入不合法。

请检查并修改输入参数后重试。

-2

RTMP_MALLOC_FAILED

内存分配失败。

请检查视频设备当前内存占用情况后重试。

-3

RTMP_CONNECT_FAILED

RTMP建立连接失败。

请检查网络是否正常后重试。

-4

RTMP_IS_DISCONNECTED

RTMP连接未建立。

该错误通常因服务端断开导致,可忽略。

-5

RTMP_UNSUPPORT_FORMAT

不支持的音视频格式。

setStreamParams接口设置的参数无效,请检查并修改后重试。

-6

RTMP_SEND_FAILED

RTMP数据包发送失败。

与服务端断开连接后再调用send接口会导致报该错误,可忽略。

-7

RTMP_READ_MESSAGE_FAILED

RTMP消息读取失败。

该错误通常因服务端断开导致,可忽略。

-8

RTMP_READ_TIMESTAMP_ERROR

输入时间戳错误。

直播推流时需保证时间戳未出现回退。请检查时间戳是否合法后重试。