android中使用previewcallback 获得的YUV数据格式一般是YV12 或者NV21. 而H264的编码格式一般是YUV420P或者YUV420SP这种格式。编码之前进行数据格式转化、程序自动检测格式设置参数。讲视频流压缩,完成推流前的准备工作. 其中转化的代码如下:
public byte[] convert(byte[] data) {
// A buffer large enough for every case
if (mBuffer == null || mBuffer.length != 3 * mSliceHeight * mStride / 2 + mYPadding) {
mBuffer = new byte[3 * mSliceHeight * mStride / 2 + mYPadding];
}
if (!mPlanar) {
if (mSliceHeight == mHeight && mStride == mWidth) {
// Swaps U and V
if (!mPanesReversed) {
for (int i = mSize; i < mSize + mSize / 2; i += 2) {
mBuffer[0] = data[i + 1];
data[i + 1] = data[i];
data[i] = mBuffer[0];
}
}
if (mYPadding > 0) {
System.arraycopy(data, 0, mBuffer, 0, mSize);
System.arraycopy(data, mSize, mBuffer, mSize + mYPadding, mSize / 2);
return mBuffer;
}
return data;
}
}
else {
if (mSliceHeight == mHeight && mStride == mWidth) {
// De-interleave U and V
if (!mPanesReversed) {
for (int i = 0; i < mSize / 4; i += 1) {
mBuffer[i] = data[mSize + 2 * i + 1];
mBuffer[mSize / 4 + i] = data[mSize + 2 * i];
}
}
else {
for (int i = 0; i < mSize / 4; i += 1) {
mBuffer[i] = data[mSize + 2 * i];
mBuffer[mSize / 4 + i] = data[mSize + 2 * i + 1];
}
}
if (mYPadding == 0) {
System.arraycopy(mBuffer, 0, data, mSize, mSize / 2);
}
else {
System.arraycopy(data, 0, mBuffer, 0, mSize);
System.arraycopy(mBuffer, 0, mBuffer, mSize + mYPadding, mSize / 2);
return mBuffer;
}
return data;
}
}
return data;
}
编码器自动检测格式:
public byte[] convert(byte[] data) {
// A buffer large enough for every case
if (mBuffer == null || mBuffer.length != 3 * mSliceHeight * mStride / 2 + mYPadding) {
mBuffer = new byte[3 * mSliceHeight * mStride / 2 + mYPadding];
}
if (!mPlanar) {
if (mSliceHeight == mHeight && mStride == mWidth) {
// Swaps U and V
if (!mPanesReversed) {
for (int i = mSize; i < mSize + mSize / 2; i += 2) {
mBuffer[0] = data[i + 1];
data[i + 1] = data[i];
data[i] = mBuffer[0];
}
}
if (mYPadding > 0) {
System.arraycopy(data, 0, mBuffer, 0, mSize);
System.arraycopy(data, mSize, mBuffer, mSize + mYPadding, mSize / 2);
return mBuffer;
}
return data;
}
}
else {
if (mSliceHeight == mHeight && mStride == mWidth) {
// De-interleave U and V
if (!mPanesReversed) {
for (int i = 0; i < mSize / 4; i += 1) {
mBuffer[i] = data[mSize + 2 * i + 1];
mBuffer[mSize / 4 + i] = data[mSize + 2 * i];
}
}
else {
for (int i = 0; i < mSize / 4; i += 1) {
mBuffer[i] = data[mSize + 2 * i];
mBuffer[mSize / 4 + i] = data[mSize + 2 * i + 1];
}
}
if (mYPadding == 0) {
System.arraycopy(mBuffer, 0, data, mSize, mSize / 2);
}
else {
System.arraycopy(data, 0, mBuffer, 0, mSize);
System.arraycopy(mBuffer, 0, mBuffer, mSize + mYPadding, mSize / 2);
return mBuffer;
}
return data;
}
}
return data;
}
检测格式配置
private void debug() {
// If testing the phone again is not needed,
// we just restore the result from the shared preferences
if (!checkTestNeeded()) {
String resolution = mWidth + "x" + mHeight + "-";
boolean success = mPreferences.getBoolean(PREF_PREFIX + resolution
+ "success", false);
if (!success) {
throw new RuntimeException(
"Phone not supported with this resolution (" + mWidth
+ "x" + mHeight + ")");
}
mNV21.setSize(mWidth, mHeight);
mNV21.setSliceHeigth(mPreferences.getInt(PREF_PREFIX + resolution
+ "sliceHeight", 0));
mNV21.setStride(mPreferences.getInt(PREF_PREFIX + resolution
+ "stride", 0));
mNV21.setYPadding(mPreferences.getInt(PREF_PREFIX + resolution
+ "padding", 0));
mNV21.setPlanar(mPreferences.getBoolean(PREF_PREFIX + resolution
+ "planar", false));
mNV21.setColorPanesReversed(mPreferences.getBoolean(PREF_PREFIX
+ resolution + "reversed", false));
mEncoderName = mPreferences.getString(PREF_PREFIX + resolution
+ "encoderName", "");
mEncoderColorFormat = mPreferences.getInt(PREF_PREFIX + resolution
+ "colorFormat", 0);
mB64PPS = mPreferences.getString(PREF_PREFIX + resolution + "pps",
"");
mB64SPS = mPreferences.getString(PREF_PREFIX + resolution + "sps",
"");
return;
}
if (VERBOSE)
Log.d(TAG, ">>>> Testing the phone for resolution " + mWidth + "x"
+ mHeight);
// Builds a list of available encoders and decoders we may be able to
// use
// because they support some nice color formats
Codec[] encoders = CodecManager.findEncodersForMimeType(MIME_TYPE);
Codec[] decoders = CodecManager.findDecodersForMimeType(MIME_TYPE);
int count = 0, n = 1;
for (int i = 0; i < encoders.length; i++) {
count += encoders[i].formats.length;
}
// Tries available encoders
for (int i = 0; i < encoders.length; i++) {
for (int j = 0; j < encoders[i].formats.length; j++) {
reset();
mEncoderName = encoders[i].name;
mEncoderColorFormat = encoders[i].formats[j];
if (VERBOSE)
Log.v(TAG, ">> Test " + (n++) + "/" + count + ": "
+ mEncoderName + " with color format "
+ mEncoderColorFormat + " at " + mWidth + "x"
+ mHeight);
// Converts from NV21 to YUV420 with the specified parameters
mNV21.setSize(mWidth, mHeight);
mNV21.setSliceHeigth(mHeight);
mNV21.setStride(mWidth);
mNV21.setYPadding(0);
mNV21.setEncoderColorFormat(mEncoderColorFormat);
// /!\ NV21Convertor can directly modify the input
createTestImage();
mData = mNV21.convert(mInitialImage);
try {
// Starts the encoder
configureEncoder();
searchSPSandPPS();
saveTestResult(true);
Log.v(TAG, "The encoder " + mEncoderName
+ " is usable with resolution " + mWidth + "x"
+ mHeight);
return;
} catch (Exception e) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
e.printStackTrace(pw);
String stack = sw.toString();
String str = "Encoder " + mEncoderName
+ " cannot be used with color format "
+ mEncoderColorFormat;
if (VERBOSE)
Log.e(TAG, str, e);
mErrorLog += str + "\n" + stack;
e.printStackTrace();
} finally {
releaseEncoder();
}
}
}
saveTestResult(false);
Log.e(TAG, "No usable encoder were found on the phone for resolution "
+ mWidth + "x" + mHeight);
throw new RuntimeException(
"No usable encoder were found on the phone for resolution "
+ mWidth + "x" + mHeight);
}
/*
SD (Low quality) SD (High quality) HD 720p
1 HD 1080p
1
Video resolution 320 x 240 px 720 x 480 px 1280 x 720 px 1920 x 1080 px
Video frame rate 20 fps 30 fps 30 fps 30 fps
Video bitrate 384 Kbps 2 Mbps 4 Mbps 10 Mbps
*/
int framerate = 20;
// if (width == 640 || height == 640) {
// bitrate = 2000000;
// } else if (width == 1280 || height == 1280) {
// bitrate = 4000000;
// } else {
// bitrate = 2 * width * height;
// }
int bitrate = (int) (mWidth*mHeight*20*2*0.07f);
// int bitrate = 2 * mWidth * mHeight / 3;
EncoderDebugger debugger = EncoderDebugger.debug(mContext, mWidth, mHeight);
mVideoConverter = debugger.getNV21Convertor();
mMediaCodec = MediaCodec.createByCodecName(debugger.getEncoderName());
MediaFormat mediaFormat = MediaFormat.createVideoFormat("video/avc", mWidth, mHeight);
mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, bitrate);
mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, framerate);
mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, debugger.getEncoderColorFormat());
mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1);
mMediaCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
mMediaCodec.start();
Bundle params = new Bundle();
params.putInt(MediaCodec.PARAMETER_KEY_REQUEST_SYNC_FRAME, 0);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
mMediaCodec.setParameters(params);
}
}
上传之前进行数据转化,然后编码推送数据到流媒体服务器:
public int onVideo(byte[] data, int format) {
if (!mVideoStarted)return 0;
data = mVideoConverter.convert(data);
inputBuffers = mMediaCodec.getInputBuffers();
outputBuffers = mMediaCodec.getOutputBuffers();
int bufferIndex = mMediaCodec.dequeueInputBuffer(0);
// MainService.mEasyPusher.addwatermarkScale(data, Scaler, data.length,Constants.RECORD_VIDEO_WIDTH,Constants.RECORD_VIDEO_HEIGHT,Constants.UPLOAD_VIDEO_WIDTH,Constants.UPLOAD_VIDEO_HEIGHT);
if (bufferIndex >= 0) {
ByteBuffer buffer = null;
buffer = inputBuffers[bufferIndex];
buffer.clear();
buffer.put(data);
buffer.clear();
mMediaCodec.queueInputBuffer(bufferIndex, 0, data.length, System.nanoTime() / 1000, 0);
}
return 0;
}
@Override
public void run(){
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
int outputBufferIndex = 0;
byte[] mPpsSps = new byte[0];
byte[]h264 = new byte[mWidth*mHeight*3/2];
do {
outputBufferIndex = mMediaCodec.dequeueOutputBuffer(bufferInfo, 30000);
if (outputBufferIndex == MediaCodec.INFO_TRY_AGAIN_LATER) {
// no output available yet
} else if (outputBufferIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
// not expected for an encoder
} else if (outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
/* EasyMuxer muxer = mMuxer;
if (muxer != null) {
// should happen before receiving buffers, and should only happen once
MediaFormat newFormat = mMediaCodec.getOutputFormat();
muxer.addTrack(newFormat, true);
}*/
} else if (outputBufferIndex < 0) {
// let's ignore it
} else {
ByteBuffer outputBuffer;
outputBuffer = outputBuffers[outputBufferIndex];
Muxer muxer = mMuxer;
if (muxer != null) {
muxer.pumpStream(outputBuffer, bufferInfo, true);
}
boolean sync = false;
if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {// sps
sync = (bufferInfo.flags & MediaCodec.BUFFER_FLAG_SYNC_FRAME) != 0;
if (!sync) {
byte[] temp = new byte[bufferInfo.size];
outputBuffer.get(temp);
mPpsSps = temp;
mMediaCodec.releaseOutputBuffer(outputBufferIndex, false);
continue;
} else {
mPpsSps = new byte[0];
}
}
sync |= (bufferInfo.flags & MediaCodec.BUFFER_FLAG_SYNC_FRAME) != 0;
int len = mPpsSps.length + bufferInfo.size;
if (len > h264.length){
h264 = new byte[len];
}
if (sync) {
System.arraycopy(mPpsSps, 0, h264, 0, mPpsSps.length);
outputBuffer.get(h264, mPpsSps.length, bufferInfo.size);
mPusher.SendBuffer_org( h264, mPpsSps.length + bufferInfo.size, (int)(bufferInfo.presentationTimeUs / 1000),1, m_index);
}else{
outputBuffer.get(h264, 0, bufferInfo.size);
mPusher.SendBuffer_org( h264, bufferInfo.size, (int)(bufferInfo.presentationTimeUs / 1000), 1, m_index);
}
mMediaCodec.releaseOutputBuffer(outputBufferIndex, false);
}
}
while (mVideoStarted);
}
相关代码下载:https://github.com/Car-eye-team/Car-eye-device 欢迎加入QQ技术讨论群:590411159