设备端Android版本LinkVisual SDK提供录像播放功能,本文介绍实现录像播放功能的过程。
下文简称设备端Android版本LinkVisual SDK为LinkVisual SDK。
前提条件
背景信息
录像播放功能通过RTMP协议推流。其支持的视频编码格式和音频编码格式如下:
视频编码格式:H.264和H.265。
音频编码格式:G711a、G711u和AAC_LC。
操作步骤
步骤一:注册录像播放事件监听器和流错误监听器。
录像播放事件监听器
OnVodStreamListener
:通知开始推流、结束推流、暂停、恢复、Seek等事件。流错误监听器
OnStreamErrorListener
:通知推流中发送的错误。
步骤二:处理查询设备端录像文件列表的请求。
应用端App发起查询设备端录像文件列表的请求。
设备端收到调用查询设备录像文件列表的请求。
查询设备录像文件列表的请求,由调用IPC设备的同步设备服务完成。设备服务的详细信息,请参考设备服务调用。
设备端响应请求,并将查询范围内的文件列表返回至应用端App。
录像文件名需进行Base64编码。
@Override
public void onNotify(String connectId, String topic, AMessage aMessage){
Log.d(TAG, "onNotify() called with: connectId = [" + connectId + "], topic = [" + topic + "], aMessage = ["
+ new String((byte[]) aMessage.data) + "]");
/**
* 添加SDK的监听
*/
IPCDev.getInstance().notifySyncTopicReceived(connectId, topic, aMessage);
// 处理同步服务调用
if (CONNECT_ID.equals(connectId) && !TextUtils.isEmpty(topic) &&
topic.contains("rrpc")) {
Log.d(TAG, "IConnectNotifyListener onNotify() called with: connectId = [" + connectId + "], topic = ["
+ topic + "], aMessage = ["
+ new String((byte[]) aMessage.data) + "]");
int code = 200;
String data = "{}";
JSONObject json = JSON.parseObject(new String((byte[]) aMessage.data));
if (json != null) {
String method = json.getString("method");
JSONObject params = json.getJSONObject("params");
switch (method) {
// 查询设备录像列表请求
case "thing.service.QueryRecordList":
int beginTime = params.getIntValue("BeginTime");
int endTime = params.getIntValue("EndTime");
int querySize = params.getIntValue("QuerySize");
int type = params.getIntValue("Type");
appendLog("收到查询设备录像列表的请求: beginTime=" + beginTime +
"\tendTime=" + endTime + "\tquerySize=" + querySize + "\ttype=" + type);
JSONArray resultArray = new JSONArray();
JSONObject item1 = new JSONObject();
item1.put("FileName", Base64.encode("file1".getBytes(), Base64.DEFAULT));
item1.put("BeginTime", System.currentTimeMillis() / 1000 - 200);
item1.put("EndTime", System.currentTimeMillis() / 1000 - 100);
item1.put("Size", 1024000);
item1.put("Type", 0);
resultArray.add(item1);
JSONObject item2 = new JSONObject();
item2.put("FileName", Base64.encode("file2".getBytes(), Base64.DEFAULT));
item2.put("BeginTime", System.currentTimeMillis() / 1000 - 100);
item2.put("EndTime", System.currentTimeMillis() / 1000);
item2.put("Size", 1024000);
item2.put("Type", 0);
resultArray.add(item2);
JSONObject result = new JSONObject();
result.put("RecordList", resultArray);
code = 200;
data = result.toJSONString();
break;
default:
break;
}
}
MqttPublishRequest request = new MqttPublishRequest();
request.isRPC = false;
request.topic = topic.replace("request", "response");
String resId = topic.substring(topic.indexOf("rrpc/request/") + 13);
request.msgId = resId;
request.payloadObj = "{\"id\":\"" + resId + "\", \"code\":" + code + ",\"data\":" + data + "}";
LinkKit.getInstance().publish(request, new IConnectSendListener() {
@Override
public void onResponse(ARequest aRequest, AResponse aResponse) {
appendLog("上报成功");
}
@Override
public void onFailure(ARequest aRequest, AError aError) {
appendLog("上报失败:" + aError.toString());
}
});
}
}
步骤三:处理开始推流指令。
应用端App请求步骤二返回的录像文件列表中的文件。
服务端下发推流指令,提供回调
OnVodStreamListener.onStartPushVodStreaming(int streamId, String fileName)
或OnVodStreamListener.onStartPushVodStreaming(int streamId, int beginTimeUtc, int endTimeUtc)
通知视频设备端进行音视频数据推流,即录像播放。LinkVisual SDK提供了下面两种录像播放的方式:根据文件名播放设备端录像
@Override public void onStartPushVodStreaming(int streamId, String fileName){ appendLog("开始推点播流 " + streamId + " 文件名:" + new String(Base64.decode(fileName, Base64.NO_WRAP))); try { // 构造视频参数 VideoStreamParams videoStreamParams = new VideoStreamParams(); // 该视频文件的时长,单位为秒 videoStreamParams.setDurationInS(H264_DURATION_IN_S); 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 读取fileName文件,调用发送音视频数据接口进行推流 // 文件推流完毕后应调用 IPCDev.getInstance().getIpcStreamManager().notifyVodComplete(streamId) 通知推流完成 } catch (NoSuchStreamException e) { e.printStackTrace(); } }
根据录像时间播放设备端录像
@Override public void onStartPushVodStreaming(int streamId, int beginTimeUtc, int endTimeUtc){ appendLog("开始推点播流 " + streamId + " beginTimeUtc: "+beginTimeUtc + " endTimeUtc:"+endTimeUtc); //TODO 推流逻辑需要添加: // 1. beginTimeUtc和endTimeUtc是一天的开始和结束时间 // 2. 当收到onStartPushVodStreaming回调后,应从beginTimeUtc开始向后最近的I帧开始推流,时间戳应使用对应帧的UTC时间 // 3. 若beginTimeUtc到endTimeUtc范围内没有录像或范围内推流已经完成了,则应调用 IPCDev.getInstance().getIpcStreamManager().notifyVodComplete(streamId) 通知推流完成 // 4. 只要是beginTimeUtc到endTimeUtc范围内有数据, 即使跨文件,推流应该持续不断 }
步骤四:处理暂停推流指令或恢复推流指令。
通过响应暂停推流指令或恢复推流指令OnVodStreamListener
,暂停或恢复发送音视频数据。
/**
* 收到暂停推流请求
*
* @param streamId 流ID
*/
void onPausePushVodStreaming(int streamId);
/**
* 收到恢复推流的请求
*
* @param streamId 流ID
*/
void onResumePushVodStreaming(int streamId);
步骤五:处理Seek指令。
响应Seek指令时,会回调onSeekTo
方法,然后从timeStampInS
时间点最近的I帧开始继续推流。例如应用端App的播放器进度条Seek到80秒时,会从80秒最近的I帧开始继续推流。
/**
* 收到重新定位请求
*
* @param streamId 流ID
* @param timeStampInS 时间偏移量,相对于视频开始时间,单位为秒
*/
void onSeekTo(int streamId, long timeStampInS);
步骤六:处理停止推流指令。
当服务端下发停止推流请求时:
通过回调
OnVodStreamListener.onStopPushLiveStreaming()
方法通知设备端停止推流。通常情况下需要停止调用音视频发送接口,关闭视频文件,并调用
IPCStreamManager.getInstance().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)
方法接收和处理流错误。错误码详细信息,请参考本文下方错误码。
错误码
流错误码
错误码 | 标志符 | 描述 | 解决方法 |
---|---|---|---|
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 | 无效的流参数。 |
|
RTMP错误码
错误码 | 标志符 | 描述 | 解决方法 |
---|---|---|---|
-1 | RTMP_ILLEGAL_INPUT | 输入不合法。 | 请检查并修改输入参数后重试。 |
-2 | RTMP_MALLOC_FAILED | 内存分配失败。 | 请检查视频设备当前内存占用情况后重试。 |
-3 | RTMP_CONNECT_FAILED | RTMP建立连接失败。 | 请检查网络是否正常后重试。 |
-4 | RTMP_IS_DISCONNECTED | RTMP连接未建立。 | 该错误通常因服务端断开导致,可忽略。 |
-5 | RTMP_UNSUPPORT_FORMAT | 不支持的音视频格式。 |
|
-6 | RTMP_SEND_FAILED | RTMP数据包发送失败。 | 与服务端断开连接后再调用send接口会导致报该错误,可忽略。 |
-7 | RTMP_READ_MESSAGE_FAILED | RTMP消息读取失败。 | 该错误通常因服务端断开导致,可忽略。 |
-8 | RTMP_READ_TIMESTAMP_ERROR | 输入时间戳错误。 | 直播推流时需保证时间戳未出现回退。请检查时间戳是否合法后重试。 |