您现在的位置是:首页 >其他 >安卓播放H264/H265实时流(安卓实时预览H264/H265 安卓实时预览AVC/HEVC)网站首页其他

安卓播放H264/H265实时流(安卓实时预览H264/H265 安卓实时预览AVC/HEVC)

直播技术开发交流 2024-06-17 10:29:39
简介安卓播放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

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。