关于h.264格式的视频文件播放,我们可以通过android.media.MediaCodec这个接口去解码实现,这个对搞流媒体,安防实时视频传输的朋友应该很熟悉,这边只是个demo,有需要的同学自行下载源码研究吧!
效果图如下:
关键代码如下:
package com.asir.h264demo;
import android.annotation.TargetApi;
import android.app.Activity;
import android.media.MediaCodec;
import android.media.MediaFormat;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.view.SurfaceView;
import android.view.View;
import android.widget.Button;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
public class MainActivity extends Activity {
private SurfaceView mSurfaceView;
private Button mReadButton;
private MediaCodec mCodec;
Thread readFileThread;
boolean isInit = false;
// Video Constants
private final static String MIME_TYPE = "video/avc"; // H.264 Advanced Video
private static int VIDEO_WIDTH = 1024;
private static int VIDEO_HEIGHT = 600;
private final static int TIME_INTERNAL = 30;
private final static int HEAD_OFFSET = 512;
private final static String LOG_TAG = "asir";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mSurfaceView = (SurfaceView) findViewById(R.id.surfaceView);
mReadButton = (Button) findViewById(R.id.btn_readfile);
mReadButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (!isInit) {
initDecoder();
isInit = true;
}
readFileThread = new Thread(readFile);
readFileThread.start();
}
});
}
@Override
protected void onPause() {
super.onPause();
}
@Override
protected void onResume() {
super.onResume();
}
@Override
protected void onDestroy() {
super.onDestroy();
readFileThread.interrupt();
}
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
public void initDecoder() {
try {
mCodec = MediaCodec.createDecoderByType(MIME_TYPE);
MediaFormat mediaFormat = MediaFormat.createVideoFormat(MIME_TYPE, VIDEO_WIDTH, VIDEO_HEIGHT);
mCodec.configure(mediaFormat, mSurfaceView.getHolder().getSurface(), null, 0);
mCodec.start();
} catch (IOException e) {
e.printStackTrace();
}
}
int mCount = 0;
// final byte[] h264Split = {0, 0, 0, 1};
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
public boolean onFrame(byte[] buf, int length) {
Log.e(LOG_TAG, "onFrame start");
// Get input buffer index
long ptsUsec = 0;
if (mCount == 0) {
ByteBuffer[] inputBuffers = mCodec.getInputBuffers();
int inputBufferIndex = mCodec.dequeueInputBuffer(100000);
Log.e(LOG_TAG, "onFrame index:" + inputBufferIndex);
if (inputBufferIndex >= 0) {
ByteBuffer inputBuffer = inputBuffers[inputBufferIndex];
inputBuffer.clear();
inputBuffer.put(buf, 0, length).rewind();
mCodec.queueInputBuffer(inputBufferIndex, 0, length, 0, MediaCodec.BUFFER_FLAG_CODEC_CONFIG);
} else {
Log.e(LOG_TAG, "inputBufferIndex < 0 " + inputBufferIndex);
return false;
}
} else {
ByteBuffer[] inputBuffers = mCodec.getInputBuffers();
int inputBufferIndex = mCodec.dequeueInputBuffer(100000);
Log.e(LOG_TAG, "onFrame index:" + inputBufferIndex);
if (inputBufferIndex >= 0) {
inputBuffers[inputBufferIndex].clear();
inputBuffers[inputBufferIndex].put(buf, 0, length).rewind();
mCodec.queueInputBuffer(inputBufferIndex, 0, length, ptsUsec, 0);
} else {
Log.e(LOG_TAG, "inputBufferIndex < 0 " + inputBufferIndex);
return false;
}
}
mCount++;
// Get output buffer index
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
int outputBufferIndex = mCodec.dequeueOutputBuffer(bufferInfo, 10000);
while (outputBufferIndex >= 0) {
mCodec.releaseOutputBuffer(outputBufferIndex, true);
outputBufferIndex = mCodec.dequeueOutputBuffer(bufferInfo, 0);
}
Log.e(LOG_TAG, "onFrame end");
return true;
}
/**
* Find H264 frame head
*
* @param buffer
* @param len
* @return the offset of frame head, return 0 if can not find one
*/
static int findHead(byte[] buffer, int len) {
int i;
for (i = HEAD_OFFSET; i < len; i++) {
if (checkHead(buffer, i))
break;
}
if (i == len)
return 0;
if (i == HEAD_OFFSET)
return 0;
return i;
}
/**
* Check if is H264 frame head
*
* @param buffer
* @param offset
* @return whether the src buffer is frame head
*/
static boolean checkHead(byte[] buffer, int offset) {
// 00 00 00 01
if (buffer[offset] == 0 && buffer[offset + 1] == 0
&& buffer[offset + 2] == 0 && buffer[3] == 1)
return true;
// 00 00 01
if (buffer[offset] == 0 && buffer[offset + 1] == 0
&& buffer[offset + 2] == 1)
return true;
return false;
}
Runnable readFile = new Runnable() {
@Override
public void run() {
boolean readFlag = true;
int h264Count = 0;
while (!Thread.interrupted() && readFlag) {
if (h264Count > 300) {
try {
Thread.sleep(20);
h264Count = 0;
continue;
} catch (Exception e) {
Log.e(LOG_TAG, "Exception for: " + e.getMessage());
}
}
BufferedInputStream bis = null;
String sdFileName = "/sdcard/" + String.format("%03d", h264Count) + ".h264";
String assetsFileName = String.format("%03d", h264Count) + ".h264";
Log.e(LOG_TAG, "Read from fileName: " + sdFileName);
// FileInputStream fos = null;
try {
File h264 = new File(sdFileName);
if (!h264.exists()) {
Log.e(LOG_TAG, "File isn't exist: " + sdFileName);
return;
}
// 读取SD卡
// fos = new FileInputStream(h264);
// bis = new BufferedInputStream(fos);
// 读取assets目录
InputStream is = getResources().getAssets().open(assetsFileName);
bis = new BufferedInputStream(is);
int fileLen = (int) h264.length();
byte[] buffer = new byte[fileLen];
try {
bis.read(buffer);
} catch (IOException e) {
Log.e(LOG_TAG, "error: read from file " + e.getMessage());
}
onFrame(buffer, fileLen);
} catch (Exception e) {
if (BuildConfig.DEBUG) {
Log.e(LOG_TAG, " Error: " + e.getMessage());
}
}
h264Count++;
}
Log.d(LOG_TAG, "end loop");
}
};
}
代码逻辑分析:这边主要是通过一个线程thread去读取工程目录assets目录下的h264文件,然后将读取的内容和要进行界面显示的SurfaceView传入到MediaCodec中即可实现播放,
xml界面文件:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<SurfaceView
android:id="@+id/surfaceView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true" />
<Button
android:id="@+id/btn_readfile"
android:layout_width="100dp"
android:layout_height="80dp"
android:layout_centerInParent="true"
android:text="Start"
android:textColor="@android:color/white"
android:textSize="30dp" />
</RelativeLayout>