h.265這個視頻是不少播放器不支持的,就算是bilibili開源的ijkplayer也不能直接播放,須要本身去從新編譯 才能夠支持。 這裏經過這個demo來演示一下如何硬解碼視頻,播放h.265視頻,其實編碼的視頻一樣道理。git
視頻的播放主要在surfaceView中顯示,而解碼過程則在音頻解碼線程和視頻解碼線程兩個線程中分別執行。github
主要是用到了一個MediaCodec這個類來進行解碼。緩存
MediaExtractor mediaExtractor = new MediaExtractor();
try {
mediaExtractor.setDataSource(path); // 設置數據源
} catch (IOException e1) {
e1.printStackTrace();
}
複製代碼
視頻的mineType是video類型。ide
String mimeType = null;
for (int i = 0; i < mediaExtractor.getTrackCount(); i++) { // 信道總數
MediaFormat format = mediaExtractor.getTrackFormat(i); // 音頻文件信息
mimeType = format.getString(MediaFormat.KEY_MIME);
if (mimeType.startsWith("video/")) { // 視頻信道
mediaExtractor.selectTrack(i); // 切換到視頻信道
try {
mediaCodec = MediaCodec.createDecoderByType(mimeType); // 建立解碼器,提供數據輸出
} catch (IOException e) {
e.printStackTrace();
}
mediaCodec.configure(format, surface, null, 0);
break;
}
}
mediaCodec.start(); // 啓動MediaCodec ,等待傳入數據
複製代碼
// 輸入
ByteBuffer[] inputBuffers = mediaCodec.getInputBuffers(); // 用來存放目標文件的數據
// 輸出
ByteBuffer[] outputBuffers = mediaCodec.getOutputBuffers(); // 解碼後的數據
MediaCodec.BufferInfo info = new MediaCodec.BufferInfo(); // 用於描述解碼獲得的byte[]數據的相關信息
複製代碼
while (!Thread.interrupted()) {
if (!bIsEos) {
int inIndex = mediaCodec.dequeueInputBuffer(0);
if (inIndex >= 0) {
ByteBuffer buffer = inputBuffers[inIndex];
int nSampleSize = mediaExtractor.readSampleData(buffer, 0); // 讀取一幀數據至buffer中
if (nSampleSize < 0) {
Log.d(TAG, "InputBuffer BUFFER_FLAG_END_OF_STREAM");
mediaCodec.queueInputBuffer(inIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
bIsEos = true;
} else {
// 填數據
mediaCodec.queueInputBuffer(inIndex, 0, nSampleSize, mediaExtractor.getSampleTime(), 0); // 通知MediaDecode解碼剛剛傳入的數據
mediaExtractor.advance(); // 繼續下一取樣
}
}
}
int outIndex = mediaCodec.dequeueOutputBuffer(info, 0);
switch (outIndex) {
case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED:
Log.d(TAG, "INFO_OUTPUT_BUFFERS_CHANGED");
outputBuffers = mediaCodec.getOutputBuffers();
break;
case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED:
Log.d(TAG, "New format " + mediaCodec.getOutputFormat());
break;
case MediaCodec.INFO_TRY_AGAIN_LATER:
Log.d(TAG, "dequeueOutputBuffer timed out!");
break;
default:
ByteBuffer buffer = outputBuffers[outIndex];
Log.v(TAG, "We can't use this buffer but render it due to the API limit, " + buffer);
mediaCodec.releaseOutputBuffer(outIndex, true);
break;
}
if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
Log.d("DecodeActivity", "OutputBuffer BUFFER_FLAG_END_OF_STREAM");
break;
}
}
複製代碼
mediaCodec.stop();
mediaCodec.release();
mediaExtractor.release();
複製代碼
這樣視頻的解碼就已經完成了,此時surfaceView已經能夠播放視頻了,接下來是音頻解碼。this
音頻解碼的過程和上面大同小異,主要區別在於,視頻是用surfaceView播放顯示的,而音頻咱們須要使用AudioTrack來播放。編碼
public class AudioPlayer {
private int mFrequency;// 採樣率
private int mChannel;// 聲道
private int mSampBit;// 採樣精度
private AudioTrack mAudioTrack;
public AudioPlayer(int frequency, int channel, int sampbit) {
this.mFrequency = frequency;
this.mChannel = channel;
this.mSampBit = sampbit;
}
/**
* 初始化
*/
public void init() {
if (mAudioTrack != null) {
release();
}
// 得到構建對象的最小緩衝區大小
int minBufSize = AudioTrack.getMinBufferSize(mFrequency, mChannel, mSampBit);
mAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC,
mFrequency, mChannel, mSampBit, minBufSize, AudioTrack.MODE_STREAM);
mAudioTrack.play();
}
/**
* 釋放資源
*/
private void release() {
if (mAudioTrack != null) {
mAudioTrack.stop();
mAudioTrack.release();
}
}
/**
* 將解碼後的pcm數據寫入audioTrack播放
*
* @param data 數據
* @param offset 偏移
* @param length 須要播放的長度
*/
public void play(byte[] data, int offset, int length) {
if (data == null || data.length == 0) {
return;
}
try {
mAudioTrack.write(data, offset, length);
} catch (Exception e) {
e.printStackTrace();
}
}
}
複製代碼
音頻的mineType是audio類型,咱們根據這個來去音頻信息便可。spa
String mimeType;
for (int i = 0; i < mediaExtractor.getTrackCount(); i++) { // 信道總數
MediaFormat format = mediaExtractor.getTrackFormat(i); // 音頻文件信息
mimeType = format.getString(MediaFormat.KEY_MIME);
if (mimeType.startsWith("audio/")) { // 音頻信道
mediaExtractor.selectTrack(i); // 切換到 音頻信道
try {
mediaCodec = MediaCodec.createDecoderByType(mimeType); // 建立解碼器,提供數據輸出
} catch (IOException e) {
e.printStackTrace();
}
mediaCodec.configure(format, null, null, 0);
mPlayer = new AudioPlayer(format.getInteger(MediaFormat.KEY_SAMPLE_RATE), AudioFormat
.CHANNEL_OUT_STEREO, AudioFormat.ENCODING_PCM_16BIT);
mPlayer.init();
break;
}
}
if (mediaCodec == null) {
Log.e(TAG, "Can't find video info!");
return;
}
mediaCodec.start(); // 啓動MediaCodec ,等待傳入數據
複製代碼
音頻解碼過程與視頻解碼大同小異,只須要額外調用一下咱們建立的AudioPlayer來播放音頻便可。線程
while (!Thread.interrupted()) {
if (!bIsEos) {
int inIndex = mediaCodec.dequeueInputBuffer(0);
if (inIndex >= 0) {
ByteBuffer buffer = inputBuffers[inIndex];
int nSampleSize = mediaExtractor.readSampleData(buffer, 0); // 讀取一幀數據至buffer中
if (nSampleSize < 0) {
Log.d(TAG, "InputBuffer BUFFER_FLAG_END_OF_STREAM");
mediaCodec.queueInputBuffer(inIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
bIsEos = true;
} else {
// 填數據
mediaCodec.queueInputBuffer(inIndex, 0, nSampleSize, mediaExtractor.getSampleTime(), 0); // 通知MediaDecode解碼剛剛傳入的數據
mediaExtractor.advance(); // 繼續下一取樣
}
}
}
int outIndex = mediaCodec.dequeueOutputBuffer(info, 0);
switch (outIndex) {
case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED:
Log.d(TAG, "INFO_OUTPUT_BUFFERS_CHANGED");
outputBuffers = mediaCodec.getOutputBuffers();
break;
case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED:
Log.d(TAG, "New format " + mediaCodec.getOutputFormat());
break;
case MediaCodec.INFO_TRY_AGAIN_LATER:
Log.d(TAG, "dequeueOutputBuffer timed out!");
break;
default:
ByteBuffer buffer = outputBuffers[outIndex];
Log.v(TAG, "We can't use this buffer but render it due to the API limit, " + buffer);
while (info.presentationTimeUs / 1000 > System.currentTimeMillis() - startMs) {
try {
sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
break;
}
}
//用來保存解碼後的數據
byte[] outData = new byte[info.size];
buffer.get(outData);
//清空緩存
buffer.clear();
//播放解碼後的數據
mPlayer.play(outData, 0, info.size);
mediaCodec.releaseOutputBuffer(outIndex, true);
break;
}
// All decoded frames have been rendered, we can stop playing
// now
if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
Log.d("DecodeActivity", "OutputBuffer BUFFER_FLAG_END_OF_STREAM");
break;
}
}
複製代碼
視頻編碼信息,爲h.265:code
播放效果(帶聲音):orm
HashMap<String, MediaCodecInfo.CodecCapabilities> mEncoderInfos = new HashMap<>();
for(int i = MediaCodecList.getCodecCount() - 1; i >= 0; i--){
MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i);
if(codecInfo.isEncoder()){
for(String t : codecInfo.getSupportedTypes()){
try{
mEncoderInfos.put(t, codecInfo.getCapabilitiesForType(t));
} catch(IllegalArgumentException e){
e.printStackTrace();
}
}
}
}
複製代碼
使用的時候將assets下的h265.mp4複製到sd卡便可
gitHub地址:github.com/JavaNoober/…