parent
ef362bfa28
commit
cf3a8b2f16
@ -0,0 +1,395 @@ |
||||
package net.pokeranalytics.android.model.handhistory |
||||
|
||||
import android.app.Activity |
||||
import android.app.ProgressDialog |
||||
import android.graphics.Bitmap |
||||
import android.graphics.BitmapFactory |
||||
import android.media.* |
||||
import android.media.MediaCodecInfo.CodecCapabilities |
||||
import android.os.Environment |
||||
import android.os.Handler |
||||
import android.util.Log |
||||
import java.io.File |
||||
import java.io.IOException |
||||
import java.text.DateFormat |
||||
import java.util.* |
||||
|
||||
|
||||
class MMediaMuxer { |
||||
|
||||
private var mediaCodec: MediaCodec? = null |
||||
private var mediaMuxer: MediaMuxer? = null |
||||
private var mRunning = false |
||||
private var generateIndex = 0 |
||||
private var mTrackIndex = 0 |
||||
private var MAX_FRAME_VIDEO = 0 |
||||
private var bitList: MutableList<ByteArray>? = null |
||||
private var bitFirst: MutableList<ByteArray>? = null |
||||
private var bitLast: MutableList<ByteArray>? = null |
||||
private var current_index_frame = 0 |
||||
private var outputPath: String? = null |
||||
private var _activity: Activity? = null |
||||
private var pd: ProgressDialog? = null |
||||
private var _title: String? = null |
||||
private var _mess: String? = null |
||||
|
||||
fun Init( |
||||
activity: Activity?, |
||||
width: Int, |
||||
height: Int, |
||||
title: String?, |
||||
mess: String? |
||||
) { |
||||
_title = title |
||||
_mess = mess |
||||
_activity = activity |
||||
_width = width |
||||
_height = height |
||||
Logd("MMediaMuxer Init") |
||||
ShowProgressBar() |
||||
} |
||||
|
||||
private val aHandler = Handler() |
||||
|
||||
fun AddFrame(byteFrame: ByteArray) { |
||||
CheckDataListState() |
||||
Thread(Runnable { |
||||
Logd("Android get Frame") |
||||
val bit = BitmapFactory.decodeByteArray(byteFrame, 0, byteFrame.size) |
||||
Logd("Android convert Bitmap") |
||||
val byteConvertFrame = |
||||
getNV21(bit.width, bit.height, bit) |
||||
Logd("Android convert getNV21") |
||||
bitList!!.add(byteConvertFrame) |
||||
}).start() |
||||
} |
||||
|
||||
fun AddFrame(byteFrame: ByteArray, count: Int, isLast: Boolean) { |
||||
var byteFrame = byteFrame |
||||
CheckDataListState() |
||||
Logd("Android get Frames = $count") |
||||
val bit = BitmapFactory.decodeByteArray(byteFrame, 0, byteFrame.size) |
||||
Logd("Android convert Bitmap") |
||||
byteFrame = getNV21(bit.width, bit.height, bit) |
||||
Logd("Android convert getNV21") |
||||
for (i in 0 until count) { |
||||
if (isLast) { |
||||
bitLast!!.add(byteFrame) |
||||
} else { |
||||
bitFirst!!.add(byteFrame) |
||||
} |
||||
} |
||||
} |
||||
|
||||
fun CreateVideo() { |
||||
current_index_frame = 0 |
||||
Logd("Prepare Frames Data") |
||||
bitFirst!!.addAll(bitList!!) |
||||
bitFirst!!.addAll(bitLast!!) |
||||
MAX_FRAME_VIDEO = bitFirst!!.size |
||||
Logd("CreateVideo") |
||||
mRunning = true |
||||
bufferEncoder() |
||||
} |
||||
|
||||
fun GetStateEncoder(): Boolean { |
||||
return mRunning |
||||
} |
||||
|
||||
fun GetPath(): String? { |
||||
return outputPath |
||||
} |
||||
|
||||
fun onBackPressed() { |
||||
mRunning = false |
||||
} |
||||
|
||||
fun ShowProgressBar() { |
||||
_activity!!.runOnUiThread { |
||||
pd = ProgressDialog(_activity) |
||||
pd!!.setTitle(_title) |
||||
pd!!.setCancelable(false) |
||||
pd!!.setMessage(_mess) |
||||
pd!!.setCanceledOnTouchOutside(false) |
||||
pd!!.show() |
||||
} |
||||
} |
||||
|
||||
fun HideProgressBar() { |
||||
Thread(Runnable { _activity!!.runOnUiThread { pd!!.dismiss() } }).start() |
||||
} |
||||
|
||||
private fun bufferEncoder() { |
||||
val runnable = Runnable { |
||||
try { |
||||
Logd("PrepareEncoder start") |
||||
PrepareEncoder() |
||||
Logd("PrepareEncoder end") |
||||
} catch (e: IOException) { |
||||
Loge(e.message) |
||||
} |
||||
try { |
||||
while (mRunning) { |
||||
Encode() |
||||
} |
||||
} finally { |
||||
Logd("release") |
||||
Release() |
||||
HideProgressBar() |
||||
bitFirst = null |
||||
bitLast = null |
||||
} |
||||
} |
||||
val thread = Thread(runnable) |
||||
thread.start() |
||||
} |
||||
|
||||
fun ClearTask() { |
||||
bitList = null |
||||
bitFirst = null |
||||
bitLast = null |
||||
} |
||||
|
||||
@Throws(IOException::class) |
||||
private fun PrepareEncoder() { |
||||
val codecInfo = |
||||
selectCodec(MIME_TYPE) |
||||
if (codecInfo == null) { |
||||
Loge("Unable to find an appropriate codec for $MIME_TYPE") |
||||
} |
||||
Logd("found codec: " + codecInfo!!.name) |
||||
val colorFormat: Int |
||||
colorFormat = try { |
||||
selectColorFormat(codecInfo, MIME_TYPE) |
||||
} catch (e: Exception) { |
||||
CodecCapabilities.COLOR_FormatYUV420SemiPlanar |
||||
} |
||||
mediaCodec = MediaCodec.createByCodecName(codecInfo.name) |
||||
val mediaFormat = MediaFormat.createVideoFormat( |
||||
MIME_TYPE, |
||||
_width, |
||||
_height |
||||
) |
||||
mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, BIT_RATE) |
||||
mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, FRAME_RATE) |
||||
mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, colorFormat) |
||||
mediaFormat.setInteger( |
||||
MediaFormat.KEY_I_FRAME_INTERVAL, |
||||
INFLAME_INTERVAL |
||||
) |
||||
mediaCodec?.let { |
||||
it.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE) |
||||
it.start() |
||||
} |
||||
try { |
||||
val currentDateTimeString = |
||||
DateFormat.getDateTimeInstance().format(Date()) |
||||
outputPath = File( |
||||
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES), |
||||
"pixel$currentDateTimeString.mp4" |
||||
).toString() |
||||
mediaMuxer = MediaMuxer(outputPath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4) |
||||
} catch (ioe: IOException) { |
||||
Loge("MediaMuxer creation failed") |
||||
} |
||||
} |
||||
|
||||
private fun Encode() { |
||||
while (true) { |
||||
if (!mRunning) { |
||||
break |
||||
} |
||||
Logd("Encode start") |
||||
val TIMEOUT_USEC: Long = 5000 |
||||
val inputBufIndex = mediaCodec!!.dequeueInputBuffer(TIMEOUT_USEC) |
||||
val ptsUsec = |
||||
computePresentationTime(generateIndex.toLong(), FRAME_RATE) |
||||
if (inputBufIndex >= 0) { |
||||
val input = bitFirst!![current_index_frame] |
||||
val inputBuffer = mediaCodec!!.getInputBuffer(inputBufIndex) |
||||
inputBuffer.clear() |
||||
inputBuffer.put(input) |
||||
mediaCodec!!.queueInputBuffer(inputBufIndex, 0, input.size, ptsUsec, 0) |
||||
generateIndex++ |
||||
} |
||||
val mBufferInfo = MediaCodec.BufferInfo() |
||||
val encoderStatus = mediaCodec!!.dequeueOutputBuffer(mBufferInfo, TIMEOUT_USEC) |
||||
if (encoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER) { // no output available yet |
||||
Loge("No output from encoder available") |
||||
} else if (encoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { // not expected for an encoder |
||||
val newFormat = mediaCodec!!.outputFormat |
||||
mTrackIndex = mediaMuxer!!.addTrack(newFormat) |
||||
mediaMuxer!!.start() |
||||
} else if (encoderStatus < 0) { |
||||
Loge("unexpected result from encoder.dequeueOutputBuffer: $encoderStatus") |
||||
} else if (mBufferInfo.size != 0) { |
||||
val encodedData = mediaCodec!!.getOutputBuffer(encoderStatus) |
||||
if (encodedData == null) { |
||||
Loge("encoderOutputBuffer $encoderStatus was null") |
||||
} else { |
||||
encodedData.position(mBufferInfo.offset) |
||||
encodedData.limit(mBufferInfo.offset + mBufferInfo.size) |
||||
mediaMuxer!!.writeSampleData(mTrackIndex, encodedData, mBufferInfo) |
||||
mediaCodec!!.releaseOutputBuffer(encoderStatus, false) |
||||
} |
||||
} |
||||
current_index_frame++ |
||||
if (current_index_frame > MAX_FRAME_VIDEO - 1) { |
||||
Log.d(TAG, "mRunning = false;") |
||||
mRunning = false |
||||
} |
||||
Logd("Encode end") |
||||
} |
||||
} |
||||
|
||||
private fun Release() { |
||||
if (mediaCodec != null) { |
||||
mediaCodec!!.stop() |
||||
mediaCodec!!.release() |
||||
mediaCodec = null |
||||
Logd("RELEASE CODEC") |
||||
} |
||||
if (mediaMuxer != null) { |
||||
mediaMuxer!!.stop() |
||||
mediaMuxer!!.release() |
||||
mediaMuxer = null |
||||
Logd("RELEASE MUXER") |
||||
} |
||||
} |
||||
|
||||
private fun getNV21(inputWidth: Int, inputHeight: Int, scaled: Bitmap): ByteArray { |
||||
val argb = IntArray(inputWidth * inputHeight) |
||||
scaled.getPixels(argb, 0, inputWidth, 0, 0, inputWidth, inputHeight) |
||||
val yuv = ByteArray(inputWidth * inputHeight * 3 / 2) |
||||
encodeYUV420SP(yuv, argb, inputWidth, inputHeight) |
||||
scaled.recycle() |
||||
return yuv |
||||
} |
||||
|
||||
private fun encodeYUV420SP( |
||||
yuv420sp: ByteArray, |
||||
argb: IntArray, |
||||
width: Int, |
||||
height: Int |
||||
) { |
||||
val frameSize = width * height |
||||
var yIndex = 0 |
||||
var uvIndex = frameSize |
||||
var a: Int |
||||
var R: Int |
||||
var G: Int |
||||
var B: Int |
||||
var Y: Int |
||||
var U: Int |
||||
var V: Int |
||||
var index = 0 |
||||
for (j in 0 until height) { |
||||
for (i in 0 until width) { |
||||
a = argb[index] and -0x1000000 shr 24 // a is not used obviously |
||||
R = argb[index] and 0xff0000 shr 16 |
||||
G = argb[index] and 0xff00 shr 8 |
||||
B = argb[index] and 0xff shr 0 |
||||
Y = (66 * R + 129 * G + 25 * B + 128 shr 8) + 16 |
||||
U = (-38 * R - 74 * G + 112 * B + 128 shr 8) + 128 |
||||
V = (112 * R - 94 * G - 18 * B + 128 shr 8) + 128 |
||||
yuv420sp[yIndex++] = |
||||
(if (Y < 0) 0 else if (Y > 255) 255 else Y).toByte() |
||||
if (j % 2 == 0 && index % 2 == 0) { |
||||
yuv420sp[uvIndex++] = |
||||
(if (U < 0) 0 else if (U > 255) 255 else U).toByte() |
||||
yuv420sp[uvIndex++] = |
||||
(if (V < 0) 0 else if (V > 255) 255 else V).toByte() |
||||
} |
||||
index++ |
||||
} |
||||
} |
||||
} |
||||
|
||||
private fun CheckDataListState() { |
||||
if (bitList == null) { |
||||
bitList = ArrayList() |
||||
} |
||||
if (bitFirst == null) { |
||||
bitFirst = ArrayList() |
||||
} |
||||
if (bitLast == null) { |
||||
bitLast = ArrayList() |
||||
} |
||||
} |
||||
|
||||
private fun computePresentationTime(frameIndex: Long, framerate: Int): Long { |
||||
return 132 + frameIndex * 1000000 / framerate |
||||
} |
||||
|
||||
companion object { |
||||
private const val MIME_TYPE = "video/avc" // H.264 Advanced Video Coding |
||||
private var _width = 512 |
||||
private var _height = 512 |
||||
private const val BIT_RATE = 800000 |
||||
private const val INFLAME_INTERVAL = 1 |
||||
private const val FRAME_RATE = 10 |
||||
private const val DEBUG = false |
||||
private const val TAG = "CODEC" |
||||
/** |
||||
* Returns the first codec capable of encoding the specified MIME type, or |
||||
* null if no match was found. |
||||
*/ |
||||
private fun selectCodec(mimeType: String): MediaCodecInfo? { |
||||
val numCodecs = MediaCodecList.getCodecCount() |
||||
for (i in 0 until numCodecs) { |
||||
val codecInfo = MediaCodecList.getCodecInfoAt(i) |
||||
if (!codecInfo.isEncoder) { |
||||
continue |
||||
} |
||||
val types = codecInfo.supportedTypes |
||||
for (j in types.indices) { |
||||
if (types[j].equals(mimeType, ignoreCase = true)) { |
||||
return codecInfo |
||||
} |
||||
} |
||||
} |
||||
return null |
||||
} |
||||
|
||||
/** |
||||
* Returns a color format that is supported by the codec and by this test |
||||
* code. If no match is found, this throws a test failure -- the set of |
||||
* formats known to the test should be expanded for new platforms. |
||||
*/ |
||||
private fun selectColorFormat( |
||||
codecInfo: MediaCodecInfo, |
||||
mimeType: String |
||||
): Int { |
||||
val capabilities = codecInfo |
||||
.getCapabilitiesForType(mimeType) |
||||
for (i in capabilities.colorFormats.indices) { |
||||
val colorFormat = capabilities.colorFormats[i] |
||||
if (isRecognizedFormat(colorFormat)) { |
||||
return colorFormat |
||||
} |
||||
} |
||||
return 0 // not reached |
||||
} |
||||
|
||||
/** |
||||
* Returns true if this is a color format that this test code understands |
||||
* (i.e. we know how to read and generate frames in this format). |
||||
*/ |
||||
private fun isRecognizedFormat(colorFormat: Int): Boolean { |
||||
return when (colorFormat) { |
||||
CodecCapabilities.COLOR_FormatYUV420Planar, CodecCapabilities.COLOR_FormatYUV420PackedPlanar, CodecCapabilities.COLOR_FormatYUV420SemiPlanar, CodecCapabilities.COLOR_FormatYUV420PackedSemiPlanar, CodecCapabilities.COLOR_TI_FormatYUV420PackedSemiPlanar -> true |
||||
else -> false |
||||
} |
||||
} |
||||
|
||||
private fun Logd(Mess: String) { |
||||
if (DEBUG) { |
||||
Log.d(TAG, Mess) |
||||
} |
||||
} |
||||
|
||||
private fun Loge(Mess: String?) { |
||||
Log.e(TAG, Mess) |
||||
} |
||||
} |
||||
} |
||||
Loading…
Reference in new issue