压缩效率
- H.265
压缩效率提高了约 50% 同等视觉质量 可以将文件压缩更小 - H.264
同等比特率 文件更大或者图像质量更低
算法
- H.265
更大的预测块(64*64 像素) 改进的运动补偿和变换 - H.264
较小的块(16*16 像素)
图像质量
- H.265
低比特率和高分辨率下可以更好的保证图像质量 减少压缩伪影 - H.264
相同比特率 会出现更多伪影和细节损失
计算复杂度
- H.265
更复杂的编码和解码过程 需要更高的计算资源和处理能力 编码较慢编码较快 - H.264
编码和解码复杂度较低 适合处理能力有限的设备
分辨率和帧率
- H.265
更高的分辨率(8K) 更高的帧率 适合高质量和高分辨率视频需求 - H.264
4K以下分辨率 可以支持高分辨率但性能效率低
兼容性
- H.265
旧设备或软件不被支持 - H.264
都兼容
如何解析?
H265
编码
try {
// 创建解码器,指定要使用的解码格式
MediaCodec codec = MediaCodec.createDecoderByType("video/hevc");
MediaFormat format = MediaFormat.createVideoFormat("video/hevc", width, height);
//描述平均位速率(以位/秒为单位)的键。 关联的值是一个整数
mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, width * height * 3/ 8);
//描述视频格式的帧速率(以帧/秒为单位)的键。帧率,一般在15至30之内,太小容易造成视 频卡顿。
mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, frameRate);
//色彩格式,具体查看相关API,不同设备支持的色彩格式不尽相同
mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT,
MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar);
//关键帧间隔时间,单位是秒
mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 2);
mediaFormat.setInteger(MediaFormat.KEY_BITRATE_MODE,
MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_CBR);
// 设置解码器的一些配置, 如需要的参数
codec.configure(format, surface, null, 0);
codec.start();
} catch (IOException e) {
e.printStackTrace();
}
private void encoder(byte[] data){
try {
//拿到输入缓冲区,用于传送数据进行编码
ByteBuffer[] inputBuffers = mediaCodec.getInputBuffers();
//拿到输出缓冲区,用于取到编码后的数据
ByteBuffer[] outputBuffers = mediaCodec.getOutputBuffers();
int inputBufferIndex = mediaCodec.dequeueInputBuffer(-1);
//当输入缓冲区有效时,就是>=0
if (inputBufferIndex >= 0) {
ByteBuffer inputBuffer = inputBuffers[inputBufferIndex];
inputBuffer.clear();
//往输入缓冲区写入数据
inputBuffer.put(data);
//五个参数,第一个是输入缓冲区的索引,第二个数据是输入缓冲区起始索引,第三个是放入的数据大小,第四个是时间戳,保证递增就是
mediaCodec.queueInputBuffer(inputBufferIndex, 0, data.length, System.nanoTime() / 1000, 0);
}
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo(); // 用于存储解码或编码过程中的数据包的相关信息(比如时间戳、数据大小等)
//拿到输出缓冲区的索引
int outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, 0);
while (outputBufferIndex >= 0) {
ByteBuffer outputBuffer = outputBuffers[outputBufferIndex];
byte[] outData = new byte[bufferInfo.size];
outputBuffer.get(outData);
// 将编码后的数据写入 MediaMuxer
if (!muxerStarted) {
MediaFormat outputFormat = mediaCodec.getOutputFormat();
videoTrackIndex = mediaMuxer.addTrack(outputFormat);
mediaMuxer.start();
muxerStarted = true;
}
mediaMuxer.writeSampleData(videoTrackIndex, outputBuffer, bufferInfo); // MediaMuxer 允许你将音频和视频的编码数据合并为一个文件 比如MP4
mediaCodec.releaseOutputBuffer(outputBufferIndex, false);
outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, 0);
}
} catch (Throwable t) {
t.printStackTrace();
}
}
BufferInfo
主要字段
offset
数据缓冲区的起始位置 通常是 0 表示从缓冲区的起始位置开始读取数据size
当前数据块的大小,单位是字节 它指示了数据缓冲区中有效数据的长度presentationTimeUs
当前数据的展示时间戳 单位是微秒(microseconds) 它是数据应该被显示(或者说播放)时的时间 通常用于音视频同步 这是一个非常重要的参数 尤其是在视频播放、视频文件封装时flags
用于指示数据块的标志 这个字段表示数据的特性 如是否为关键帧(BUFFER_FLAG_KEY_FRAME)等 它可以包含多个标志位 用来描述缓冲区中的数据
解码
mediaCodec = MediaCodec.createDecoderByType(TYPE_HEVC);
mediaFormat = MediaFormat.createVideoFormat(TYPE_HEVC, mOriginalWidth, mOriginalHeight);
mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, bitrate);
mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, framerate);
mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1);
public void putData(byte[] data, long timestamp) {
if (data == null || data.length == 0) {
Log.e(TAG, "rtsp拉流无效的输入数据");
return;
}
if (queue.size() >= MAX_QUEUE_SIZE) {
queue.clear();
}
RtspDecoderData decoderData = new RtspDecoderData();
decoderData.data = data;
decoderData.timestamp = timestamp;
queue.offer(decoderData);
}
private void tryToFeedDecoder(){
try {
if (!mAvailableBuffers.isEmpty() && !queue.isEmpty() && mediaCodec != null) {
RtspDecoderData input = queue.poll();
if (input != null) {
int bufferIndex = mAvailableBuffers.remove(0);
ByteBuffer decoderInputBuffer = mediaCodec.getInputBuffer(bufferIndex);
decoderInputBuffer.clear();
decoderInputBuffer.put(input.data, 0, input.data.length);
mediaCodec.queueInputBuffer(bufferIndex, 0, input.data.length, input.timestamp, 0);
}
}
} catch (Exception exception) {
Log.d(TAG, "tryToFeedDecoder: " + exception.getMessage());
}
}
public void decode() {
mediaCodec.setCallback(new MediaCodec.Callback() {
@Override
public void onInputBufferAvailable(@NonNull MediaCodec codec, int index) {
if (mDebug) {
Log.d(TAG, "onInputBufferAvailable index: " + index);
}
mAvailableBuffers.add(index);
}
@Override
public void onOutputBufferAvailable(@NonNull MediaCodec codec, int index, @NonNull MediaCodec.BufferInfo info) {
if (mDebug) {
Log.d(TAG, "onOutputBufferAvailable index: " + index + " time: " + System.currentTimeMillis());
}
if (info.flags == MediaCodec.BUFFER_FLAG_END_OF_STREAM) {
codec.releaseOutputBuffer(index, false);
return; // 处理流结束
}
if (info.size <= 0) {
Log.w(TAG, "检测到损坏的帧,跳过");
codec.releaseOutputBuffer(index, false);
return; // 跳过损坏的帧
}
ByteBuffer outputBuffer = codec.getOutputBuffer(index);
if (outputBuffer == null) {
Log.e(TAG, "输出缓冲区为空");
codec.releaseOutputBuffer(index, false);
return;// 如果输出缓冲区无效,则跳过处理
}
mLastBuffer = outputBuffer;
if (mFishEyeCorrectionTag) {
mResultBuffer.clear();
outputBuffer.position(0);
if (mIsCut) {
long before = System.currentTimeMillis();
if (mDebug) {
Log.d(TAG, "fisheyeCutNV12ToNv12ByCl start");
}
fishEyeCorrectionNative.fisheyeCutNV12ToNv12ByCl(outputBuffer, mResultBuffer, FishEyeCorrectionManager.getInstance().mPitch, 0);
if (mDebug) {
Log.d(TAG, "fisheyeCutNV12ToNv12ByCl end=" + (System.currentTimeMillis() - before));
}
} else {
long before = System.currentTimeMillis();
if (mDebug) {
Log.d(TAG, "fisheyeRemapNV12ToNv12ByCl start");
}
fishEyeCorrectionNative.fisheyeRemapNV12ToNv12ByCl(outputBuffer, mResultBuffer, FishEyeCorrectionManager.getInstance().mPitch, 0);
if (mDebug) {
Log.d(TAG, "fisheyeRemapNV12ToNv12ByCl end=" + (System.currentTimeMillis() - before));
}
}
mResultBuffer.get(resultData);
if (callBack != null) {
callBack.callBack(resultData, true);
}
mediaCodec.releaseOutputBuffer(index, false);
} else {
outputBuffer.get(mYuvData);
if (callBack != null) {
callBack.callBack(mYuvData, false);
}
mediaCodec.releaseOutputBuffer(index, false);
}
}
@Override
public void onError(@NonNull MediaCodec codec, @NonNull MediaCodec.CodecException e) {
Log.d(TAG, "onError e: " + e.getMessage());
mediaCodec.reset();
mAvailableBuffers.clear();
}
@Override
public void onOutputFormatChanged(@NonNull MediaCodec codec, @NonNull MediaFormat format) {
}
});
mediaCodec.configure(mediaFormat, null, null, 0);
mediaCodec.start();
H264
// 初始化解码器
MediaCodec codec = MediaCodec.createDecoderByType("video/avc");
// 创建视频格式
MediaFormat format = MediaFormat.createVideoFormat("video/avc", width, height);
format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
format.setInteger(MediaFormat.KEY_FRAME_RATE, frameRate);
// 配置解码器,指定输出 Surface
Surface surface = surfaceView.getHolder().getSurface();
codec.configure(format, surface, null, 0);
codec.start();
// 送入数据
int inputBufferIndex = codec.dequeueInputBuffer(-1);
if (inputBufferIndex >= 0) {
ByteBuffer inputBuffer = codec.getInputBuffer(inputBufferIndex);
inputBuffer.clear();
inputBuffer.put(h264Data); // h264Data 是你的 H.264 编码数据
codec.queueInputBuffer(inputBufferIndex, 0, h264Data.length, 0, 0);
}
// 获取并渲染输出数据
int outputBufferIndex = codec.dequeueOutputBuffer(info, -1);
if (outputBufferIndex >= 0) {
codec.releaseOutputBuffer(outputBufferIndex, true); // 渲染到 Surface
}
// 清理资源
codec.stop();
codec.release();