壓縮效率
- H.265
壓縮效率提高了約 50% 同等視覺質(zhì)量 可以將文件壓縮更小 - H.264
同等比特率 文件更大或者圖像質(zhì)量更低
算法
- H.265
更大的預(yù)測(cè)塊(64*64 像素) 改進(jìn)的運(yùn)動(dòng)補(bǔ)償和變換 - H.264
較小的塊(16*16 像素)
圖像質(zhì)量
- H.265
低比特率和高分辨率下可以更好的保證圖像質(zhì)量 減少壓縮偽影 - H.264
相同比特率 會(huì)出現(xiàn)更多偽影和細(xì)節(jié)損失
計(jì)算復(fù)雜度
- H.265
更復(fù)雜的編碼和解碼過程 需要更高的計(jì)算資源和處理能力 編碼較慢編碼較快 - H.264
編碼和解碼復(fù)雜度較低 適合處理能力有限的設(shè)備
分辨率和幀率
- H.265
更高的分辨率(8K) 更高的幀率 適合高質(zhì)量和高分辨率視頻需求 - H.264
4K以下分辨率 可以支持高分辨率但性能效率低
兼容性
- H.265
舊設(shè)備或軟件不被支持 - H.264
都兼容
如何解析?
H265
編碼
try {
// 創(chuàng)建解碼器,指定要使用的解碼格式
MediaCodec codec = MediaCodec.createDecoderByType("video/hevc");
MediaFormat format = MediaFormat.createVideoFormat("video/hevc", width, height);
//描述平均位速率(以位/秒為單位)的鍵。 關(guān)聯(lián)的值是一個(gè)整數(shù)
mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, width * height * 3/ 8);
//描述視頻格式的幀速率(以幀/秒為單位)的鍵。幀率,一般在15至30之內(nèi),太小容易造成視 頻卡頓。
mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, frameRate);
//色彩格式,具體查看相關(guān)API,不同設(shè)備支持的色彩格式不盡相同
mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT,
MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar);
//關(guān)鍵幀間隔時(shí)間,單位是秒
mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 2);
mediaFormat.setInteger(MediaFormat.KEY_BITRATE_MODE,
MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_CBR);
// 設(shè)置解碼器的一些配置, 如需要的參數(shù)
codec.configure(format, surface, null, 0);
codec.start();
} catch (IOException e) {
e.printStackTrace();
}
private void encoder(byte[] data){
try {
//拿到輸入緩沖區(qū),用于傳送數(shù)據(jù)進(jìn)行編碼
ByteBuffer[] inputBuffers = mediaCodec.getInputBuffers();
//拿到輸出緩沖區(qū),用于取到編碼后的數(shù)據(jù)
ByteBuffer[] outputBuffers = mediaCodec.getOutputBuffers();
int inputBufferIndex = mediaCodec.dequeueInputBuffer(-1);
//當(dāng)輸入緩沖區(qū)有效時(shí),就是>=0
if (inputBufferIndex >= 0) {
ByteBuffer inputBuffer = inputBuffers[inputBufferIndex];
inputBuffer.clear();
//往輸入緩沖區(qū)寫入數(shù)據(jù)
inputBuffer.put(data);
//五個(gè)參數(shù),第一個(gè)是輸入緩沖區(qū)的索引,第二個(gè)數(shù)據(jù)是輸入緩沖區(qū)起始索引,第三個(gè)是放入的數(shù)據(jù)大小,第四個(gè)是時(shí)間戳,保證遞增就是
mediaCodec.queueInputBuffer(inputBufferIndex, 0, data.length, System.nanoTime() / 1000, 0);
}
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo(); // 用于存儲(chǔ)解碼或編碼過程中的數(shù)據(jù)包的相關(guān)信息(比如時(shí)間戳、數(shù)據(jù)大小等)
//拿到輸出緩沖區(qū)的索引
int outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, 0);
while (outputBufferIndex >= 0) {
ByteBuffer outputBuffer = outputBuffers[outputBufferIndex];
byte[] outData = new byte[bufferInfo.size];
outputBuffer.get(outData);
// 將編碼后的數(shù)據(jù)寫入 MediaMuxer
if (!muxerStarted) {
MediaFormat outputFormat = mediaCodec.getOutputFormat();
videoTrackIndex = mediaMuxer.addTrack(outputFormat);
mediaMuxer.start();
muxerStarted = true;
}
mediaMuxer.writeSampleData(videoTrackIndex, outputBuffer, bufferInfo); // MediaMuxer 允許你將音頻和視頻的編碼數(shù)據(jù)合并為一個(gè)文件 比如MP4
mediaCodec.releaseOutputBuffer(outputBufferIndex, false);
outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, 0);
}
} catch (Throwable t) {
t.printStackTrace();
}
}
BufferInfo 主要字段
offset
數(shù)據(jù)緩沖區(qū)的起始位置 通常是 0 表示從緩沖區(qū)的起始位置開始讀取數(shù)據(jù)size
當(dāng)前數(shù)據(jù)塊的大小,單位是字節(jié) 它指示了數(shù)據(jù)緩沖區(qū)中有效數(shù)據(jù)的長(zhǎng)度presentationTimeUs
當(dāng)前數(shù)據(jù)的展示時(shí)間戳 單位是微秒(microseconds) 它是數(shù)據(jù)應(yīng)該被顯示(或者說播放)時(shí)的時(shí)間 通常用于音視頻同步 這是一個(gè)非常重要的參數(shù) 尤其是在視頻播放、視頻文件封裝時(shí)flags
用于指示數(shù)據(jù)塊的標(biāo)志 這個(gè)字段表示數(shù)據(jù)的特性 如是否為關(guān)鍵幀(BUFFER_FLAG_KEY_FRAME)等 它可以包含多個(gè)標(biāo)志位 用來描述緩沖區(qū)中的數(shù)據(jù)
解碼
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拉流無效的輸入數(shù)據(jù)");
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; // 處理流結(jié)束
}
if (info.size <= 0) {
Log.w(TAG, "檢測(cè)到損壞的幀,跳過");
codec.releaseOutputBuffer(index, false);
return; // 跳過損壞的幀
}
ByteBuffer outputBuffer = codec.getOutputBuffer(index);
if (outputBuffer == null) {
Log.e(TAG, "輸出緩沖區(qū)為空");
codec.releaseOutputBuffer(index, false);
return;// 如果輸出緩沖區(qū)無效,則跳過處理
}
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");
// 創(chuàng)建視頻格式
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();
// 送入數(shù)據(jù)
int inputBufferIndex = codec.dequeueInputBuffer(-1);
if (inputBufferIndex >= 0) {
ByteBuffer inputBuffer = codec.getInputBuffer(inputBufferIndex);
inputBuffer.clear();
inputBuffer.put(h264Data); // h264Data 是你的 H.264 編碼數(shù)據(jù)
codec.queueInputBuffer(inputBufferIndex, 0, h264Data.length, 0, 0);
}
// 獲取并渲染輸出數(shù)據(jù)
int outputBufferIndex = codec.dequeueOutputBuffer(info, -1);
if (outputBufferIndex >= 0) {
codec.releaseOutputBuffer(outputBufferIndex, true); // 渲染到 Surface
}
// 清理資源
codec.stop();
codec.release();