您现在的位置是:首页 >其他 >安卓播放H264/H265实时流(安卓实时预览H264/H265 安卓实时预览AVC/HEVC)网站首页其他
安卓播放H264/H265实时流(安卓实时预览H264/H265 安卓实时预览AVC/HEVC)
实际项目中经常遇到两种场景,第一种从无人机拿H264/H265码流转GB28181等协议,转协议的同时可能还需要实时预览无人机画面; 第二种是安卓接USB外置摄像头, 由于USB2.0传输带宽有限,对于高分辨率图像, 带宽无法满足YUV图像的传输, 摄像头只好先将图像编码成MJPEG,H264或H265等格式再传输。
对于上述两种场景,安卓拿到的都是已编码的H264或H265码流,这用来转GB28181、RTSP、RTMP和录像存储很方便, 但没法直接实时预览, 实时预览需要先解码,再显示. 这样增加了不少开发成本,为了方便使用, 在我的播放器上直接增加了传H264/H265字节流接口,只要把H264/H265数据传给播放器就好,播放器负责解码(软解或硬解)显示。
下面先介绍下H264/H265 Annex B Byte stream format:
字节流格式:字节流由1到多个字节流nal_unit组成.
字节流nal_unit:{前缀码(0x000001) + nal_unit} 或 {zero_byte(0x00) + 前缀码(0x000001) + nal_unit}. 前缀码为网络字节序.
需要注意的是: 1.字节流中第一个字节流nal_unit头部可能包含1个或多个leading_zero_8bits(某些安卓硬编码器会出这样的数据); 2.字节流nal_unit单元尾部可能包含一个或多个trailing_zero_8bits.(前后额外加0字节是为了保持字节对齐).
leading_zero_8bits: 一个0x00字节.
trailing_zero_8bits: 一个0x00字节.
对于VPS, SPS, PPS前缀应是 0x00000001(H264只有SPS, PPS), An access unit的第一个nal unit的前缀应是0x00000001.
字节流NAL单元语法(H264和H265是一致的):
H264 NAL unit 语法:
常用的h264 nal_unit_type: 5(IDR), 6(SEI), 7(SPS), 8(PPS).
H265 NAL unit 语法:
H265的nal_unit_type请参考265文档。
更详细的描述请参考264和265文档,对于如何调用播放器接口,上面的描述基本够用了,播放器接口如下:
/*
* Copyright (C) 1130758427@qq.com. All rights reserved.
*/
/**
* 投递视频包给播放器
*
* @param codec_id: 编码id, 当前仅支持H264和H265, 1:H264, 2:H265
*
* @param packet: 视频数据, ByteBuffer必须是DirectBuffer, 包格式请参考H264/H265 Annex B Byte stream format, 例如:
* 0x00000001 nal_unit 0x00000001 ...
* H264 IDR: 0x00000001 sps 0x00000001 pps 0x00000001 IDR_nal_unit .... 或 0x00000001 IDR_nal_unit ....
* H265 IDR: 0x00000001 vps 0x00000001 sps 0x00000001 pps 0x00000001 IDR_nal_unit .... 或 0x00000001 IDR_nal_unit ....
*
* @param offset: 偏移量
* @param size: packet size
* @param timestamp_ms: 时间戳, 单位毫秒
* @param is_timestamp_discontinuity: 是否时间戳间断,0:未间断,1:间断
* @param is_key: 是否是关键帧, 0:非关键帧, 1:关键帧
* @param extra_data: 可选参数,可传null, 对于H264关键帧包, 如果packet不含sps和pps, 可传0x00000001 sps 0x00000001 pps
* ,对于H265关键帧包, 如果packet不含vps,sps和pps, 可传0x00000001 vps 0x00000001 sps 0x00000001 pps
* @param extra_data_size: extra_data size
* @param width: 图像宽, 可传0
* @param height: 图像高, 可传0
*
* @return {0} if successful
*/
public native int PostVideoPacketByteBuffer(long handle, int codec_id,
java.nio.ByteBuffer packet, int offset, int size, long timestamp_ms, int is_timestamp_discontinuity, int is_key,
byte[] extra_data, int extra_data_size, int width, int height);
/*
* 请参考 PostVideoPacketByteBuffer说明
*/
public native int PostVideoPacketByteArray(long handle, int codec_id,
byte[] packet, int offset, int size, long timestamp_ms, int is_timestamp_discontinuity, int is_key,
byte[] extra_data, int extra_data_size, int width, int height);
调用代码:
/*
* Copyright (C) 1130758427@qq.com. All rights reserved.
*/
// 启动播放器
private long start_play(SmartPlayerJniV2 lib_player, Context context, SurfaceView surface_view, boolean is_hardware_decoder) {
if (null ==lib_player || null == context || null == surface_view)
return 0;
long handle = lib_player.SmartPlayerOpen(context);
if (0 == handle) {
Log.e(TAG, "open player failed");
return 0;
}
// 设置0, 尽可能降低预览延时
lib_player.SmartPlayerSetBuffer(handle, 0);
lib_player.SmartPlayerSetUrl(handle, "ntexternal://*******************");
lib_player.SmartPlayerSetSurface(handle, surface_view);
// 图像等比例缩放或铺满view
lib_player.SmartPlayerSetRenderScaleMode(handle, 1);
lib_player.SmartPlayerSetFastStartup(handle, 1);
// 不要播放音频,静音就好
lib_player.SmartPlayerSetMute(handle, 1);
// 大分辨率可能需要硬解,小分辨率推荐软解,硬解延时可能大些
if (is_hardware_decoder) {
lib_player.SetSmartPlayerVideoHevcHWDecoder(handle, 1);
lib_player.SetSmartPlayerVideoHWDecoder(handle, 1);
}
// 有些场景可能需要解码出来的图像用来做分析或重新编码
// 这里可以设置yuv或rgb callback, 把图像给Caller
// lib_player.SmartPlayerSetExternalRender(handle, new RGBAExternalRender());
// lib_player.SmartPlayerSetExternalRender(handle, new I420ExternalRender());
if (0 == lib_player.SmartPlayerStartPlay(handle))
return handle;
lib_player.SmartPlayerClose(handle);
return 0;
}
// 停止播放
private void stop_play(SmartPlayerJniV2 lib_player, long handle) {
if (null == lib_player)
return;
if (0 == handle)
return;
lib_player.SmartPlayerStopPlay(handle);
lib_player.SmartPlayerClose(handle);
}
// 投递H264或H265数据给播放器
public void onVideoDataCallback(int ret, int video_codec_id, int size, int is_key_frame, long timestamp, int width, int height, long presentation_timestamp) {
if (player_handle_ !=0)
lib_player_.PostVideoPacketByteBuffer(player_handle_, video_codec_id, video_buffer_, 0, size, timestamp, 0, is_key_frame, null,0, 0, 0);
}
Android也可以用MediaCodec直接解码显示,但MediaCodec坑较多,从一个演示版到稳定版周期较长成本较高,直接在现有成熟稳定的播放器SDK上加接口实现AVC/HEVC实时预览更可行些. 我在Windows上启动一个内置rtsp server流, 安卓拉rtsp流,然后将H264/H265传给播放器显示,延时非常低(毫秒级),并支持H264和H265码流实时切换预览,分辨率实时切换预览, 也支持解码后YUV/RGB数据回调, 实时截图等功能。另外苹果的VideoToolbox只支持AVCC格式,不支持Annex B格式,需要转换后再输入,更多问题联系qq: 1130758427