1+ package com.frank.ffmpeg.kotlin.hardware
2+
3+ import android.media.MediaCodec
4+ import android.media.MediaExtractor
5+ import android.media.MediaFormat
6+ import android.os.SystemClock
7+ import android.util.Log
8+ import android.view.Surface
9+ import java.lang.Exception
10+ import java.nio.ByteBuffer
11+
12+ /* *
13+ * Extract by MediaExtractor, decode by MediaCodec, and render to Surface
14+ * Created by frank on 2020/04/06.
15+ */
16+
17+ class HardwareKotlinDecode constructor(surface : Surface , filePath : String , onDataCallback : OnDataCallback ){
18+
19+ private var mSurface : Surface ? = null
20+ private var mFilePath : String = " "
21+ private var mVideoDecodeThread : VideoDecodeThread ? = null
22+ private var mCallback : OnDataCallback ? = null
23+
24+ init {
25+ this .mSurface = surface
26+ this .mFilePath = filePath
27+ this .mCallback = onDataCallback
28+ }
29+
30+ interface OnDataCallback {
31+ fun onData (duration : Long )
32+ }
33+
34+ public fun decode () {
35+ mVideoDecodeThread = VideoDecodeThread (mFilePath)
36+ mVideoDecodeThread!! .start()
37+ }
38+
39+ public fun seekTo (seekPosition : Long ) {
40+ if (! mVideoDecodeThread!! .isInterrupted) {
41+ mVideoDecodeThread!! .seekTo(seekPosition)
42+ }
43+ }
44+
45+ public fun setPreviewing (previewing : Boolean ) {
46+ mVideoDecodeThread!! .isPreviewing = previewing;
47+ }
48+
49+ public fun release () {
50+ if (! mVideoDecodeThread!! .isInterrupted) {
51+ mVideoDecodeThread!! .interrupt()
52+ mVideoDecodeThread!! .release()
53+ mVideoDecodeThread = null
54+ }
55+ }
56+
57+ private inner class VideoDecodeThread (filePath : String ) : Thread() {
58+
59+ private var mediaExtractor : MediaExtractor ? = null
60+ private var mediaCodec : MediaCodec ? = null
61+ var isPreviewing : Boolean = false
62+ private var mFilePath : String = filePath
63+
64+ fun seekTo (seekPosition : Long ) {
65+ try {
66+ mediaExtractor!! .seekTo(seekPosition, MediaExtractor .SEEK_TO_CLOSEST_SYNC )
67+ } catch (e: IllegalStateException ) {
68+ Log .e(TAG , " seekTo error=$e " )
69+ }
70+ }
71+
72+ fun release () {
73+ try {
74+ mediaCodec!! .stop()
75+ mediaCodec!! .release()
76+ mediaExtractor!! .release()
77+ }catch (e : Exception ) {
78+ Log .e(TAG , " release error=$e " )
79+ }
80+ }
81+
82+ fun setPreviewRatio (mediaFormat : MediaFormat ) {
83+ val videoWidth = mediaFormat.getInteger(MediaFormat .KEY_WIDTH )
84+ val videoHeight = mediaFormat.getInteger(MediaFormat .KEY_HEIGHT )
85+ val previewRatio = when {
86+ videoWidth >= RATIO_1080 -> 10
87+ videoWidth >= RATIO_480 -> 6
88+ videoWidth >= RATIO_240 -> 4
89+ else -> 1
90+ }
91+ val previewWidth = videoWidth / previewRatio
92+ val previewHeight = videoHeight / previewRatio
93+ mediaFormat.setInteger(MediaFormat .KEY_WIDTH , previewWidth)
94+ mediaFormat.setInteger(MediaFormat .KEY_HEIGHT , previewHeight)
95+ }
96+
97+ override fun run () {
98+ super .run ()
99+ mediaExtractor = MediaExtractor ()
100+ var mediaFormat : MediaFormat ? = null
101+ var mimeType = " "
102+
103+ try {
104+ mediaExtractor!! .setDataSource(mFilePath)
105+ val trackCount = mediaExtractor!! .trackCount
106+ for (i in 0 .. trackCount) {
107+ mediaFormat = mediaExtractor!! .getTrackFormat(i)
108+ mimeType = mediaFormat.getString(MediaFormat .KEY_MIME )
109+ if (mimeType != null && mimeType.startsWith(" video/" )) {
110+ mediaExtractor!! .selectTrack(i)
111+ break
112+ }
113+ }
114+
115+ val width : Int = mediaFormat!! .getInteger(MediaFormat .KEY_WIDTH )
116+ val height : Int = mediaFormat.getInteger(MediaFormat .KEY_HEIGHT )
117+ val duration : Long = mediaFormat.getLong(MediaFormat .KEY_DURATION )
118+ mCallback!! .onData(duration)
119+ Log .i(TAG , " width=$width --height=$height --duration=$duration " )
120+ setPreviewRatio(mediaFormat)
121+ mediaCodec = MediaCodec .createDecoderByType(mimeType)
122+ mediaCodec!! .configure(mediaFormat, mSurface, null , 0 )
123+ mediaCodec!! .start()
124+ val inputBuffers = mediaCodec!! .getInputBuffers()
125+ val bufferInfo : MediaCodec .BufferInfo = MediaCodec .BufferInfo ()
126+
127+ while (! isInterrupted) {
128+ if (! isPreviewing) {
129+ SystemClock .sleep(SLEEP_TIME )
130+ continue
131+ }
132+ val inputIndex = mediaCodec!! .dequeueInputBuffer(DEQUEUE_TIME )
133+ if (inputIndex >= 0 ) {
134+ val inputBuffer : ByteBuffer = inputBuffers[inputIndex]
135+ val sampleSize = mediaExtractor!! .readSampleData(inputBuffer, 0 )
136+ if (sampleSize < 0 ) {
137+ mediaCodec!! .queueInputBuffer(inputIndex, 0 , 0 , 0 ,
138+ MediaCodec .BUFFER_FLAG_END_OF_STREAM )
139+ } else {
140+ mediaCodec!! .queueInputBuffer(inputIndex, 0 , sampleSize, mediaExtractor!! .sampleTime, 0 )
141+ mediaExtractor!! .advance()
142+ }
143+ }
144+ val outputIndex = mediaCodec!! .dequeueOutputBuffer(bufferInfo, DEQUEUE_TIME )
145+ when (outputIndex) {
146+ MediaCodec .INFO_OUTPUT_FORMAT_CHANGED -> Log .i(TAG , " output format changed..." )
147+ MediaCodec .INFO_TRY_AGAIN_LATER -> Log .i(TAG , " try again later..." )
148+ MediaCodec .INFO_OUTPUT_BUFFERS_CHANGED -> Log .i(TAG , " output buffer changed..." )
149+ else ->
150+ // render to surface
151+ mediaCodec!! .releaseOutputBuffer(outputIndex, true )
152+ }
153+ }
154+ } catch (e : Exception ) {
155+ Log .e(TAG , " decode error=$e " )
156+ }
157+ }
158+
159+ }
160+
161+ companion object {
162+ private val TAG = HardwareKotlinDecode ::class .java.simpleName
163+
164+ private const val DEQUEUE_TIME : Long = 10 * 1000
165+ private const val SLEEP_TIME : Long = 10
166+
167+ private const val RATIO_1080 = 1080
168+ private const val RATIO_480 = 480
169+ private const val RATIO_240 = 240
170+ }
171+ }
0 commit comments