Feature: add MediaDecodeController

pull/209/head
xufuji456 3 years ago
parent aaabe133e8
commit 83b9338386
  1. 79
      AndroidMedia/src/main/java/com/frank/androidmedia/controller/MediaDecodeController.kt
  2. 75
      AndroidMedia/src/main/java/com/frank/androidmedia/controller/MediaPlayController.kt
  3. 18
      app/src/main/java/com/frank/ffmpeg/activity/VideoPreviewActivity.kt
  4. 28
      app/src/main/java/com/frank/ffmpeg/view/VideoPreviewBar.kt

@ -1,4 +1,4 @@
package com.frank.ffmpeg.hardware package com.frank.androidmedia.controller
import android.media.MediaCodec import android.media.MediaCodec
import android.media.MediaExtractor import android.media.MediaExtractor
@ -8,11 +8,74 @@ import android.util.Log
import android.view.Surface import android.view.Surface
/** /**
* Extract by MediaExtractor, decode by MediaCodec, and render to Surface * The controller of MediaExtractor and MediaCodec.
* Created by frank on 2019/11/16. * Use MediaExtractor to extract data of media tracks.
* And use MediaCodec to decode video frame from extractor.
*
* @author frank
* @date 2022/3/18
*/ */
class HardwareDecode(private val mSurface: Surface, private val mFilePath: String, private val mCallback: OnDataCallback?) { open class MediaDecodeController(val mSurface: Surface, val mFilePath: String, val mCallback: OnDataCallback?) {
/**
*
* MediaExtractor extractor = new MediaExtractor();
* extractor.setDataSource(...);
* int numTracks = extractor.getTrackCount();
* for (int i = 0; i < numTracks; ++i) {
* MediaFormat format = extractor.getTrackFormat(i);
* String mime = format.getString(MediaFormat.KEY_MIME);
* if (weAreInterestedInThisTrack) {
* extractor.selectTrack(i);
* }
* }
* ByteBuffer inputBuffer = ByteBuffer.allocate(...)
* while (extractor.readSampleData(inputBuffer, ...) != 0) {
* int trackIndex = extractor.getSampleTrackIndex();
* long presentationTimeUs = extractor.getSampleTime();
* ...
* extractor.advance();
* }
* extractor.release();
* extractor = null;
*/
/*
// MediaCodec is typically used like this in asynchronous mode:
MediaCodec codec = MediaCodec.createByCodecName(name);
MediaFormat mOutputFormat;
codec.setCallback(new MediaCodec.Callback() {
@Override
void onInputBufferAvailable(MediaCodec mc, int inputBufferId) {
ByteBuffer inputBuffer = codec.getInputBuffer(inputBufferId);
codec.queueInputBuffer(inputBufferId, );
}
@Override
void onOutputBufferAvailable(MediaCodec mc, int outputBufferId, ) {
ByteBuffer outputBuffer = codec.getOutputBuffer(outputBufferId);
MediaFormat bufferFormat = codec.getOutputFormat(outputBufferId);
codec.releaseOutputBuffer(outputBufferId, );
}
@Override
void onOutputFormatChanged(MediaCodec mc, MediaFormat format) {
mOutputFormat = format;
}
@Override
void onError() {
}
});
codec.configure(format, );
mOutputFormat = codec.getOutputFormat();
codec.start();
// wait for processing to complete
codec.stop();
codec.release();
*/
private var videoDecodeThread: VideoDecodeThread? = null private var videoDecodeThread: VideoDecodeThread? = null
@ -53,11 +116,11 @@ class HardwareDecode(private val mSurface: Surface, private val mFilePath: Strin
private var isPreviewing: Boolean = false private var isPreviewing: Boolean = false
internal fun setPreviewing(previewing: Boolean) { fun setPreviewing(previewing: Boolean) {
this.isPreviewing = previewing this.isPreviewing = previewing
} }
internal fun seekTo(seekPosition: Long) { fun seekTo(seekPosition: Long) {
try { try {
if (mediaExtractor != null) { if (mediaExtractor != null) {
mediaExtractor!!.seekTo(seekPosition, MediaExtractor.SEEK_TO_CLOSEST_SYNC) mediaExtractor!!.seekTo(seekPosition, MediaExtractor.SEEK_TO_CLOSEST_SYNC)
@ -68,7 +131,7 @@ class HardwareDecode(private val mSurface: Surface, private val mFilePath: Strin
} }
internal fun release() { fun release() {
try { try {
if (mediaCodec != null) { if (mediaCodec != null) {
mediaCodec!!.stop() mediaCodec!!.stop()
@ -187,7 +250,7 @@ class HardwareDecode(private val mSurface: Surface, private val mFilePath: Strin
companion object { companion object {
private val TAG = HardwareDecode::class.java.simpleName private val TAG = MediaPlayController::class.java.simpleName
private const val DEQUEUE_TIME = (10 * 1000).toLong() private const val DEQUEUE_TIME = (10 * 1000).toLong()
private const val SLEEP_TIME = 10 private const val SLEEP_TIME = 10

@ -1,16 +1,18 @@
package com.frank.androidmedia.controller package com.frank.androidmedia.controller
import android.media.MediaPlayer import android.media.MediaPlayer
import android.media.PlaybackParams
import android.view.Surface import android.view.Surface
import com.frank.androidmedia.listener.PlayerCallback import com.frank.androidmedia.listener.PlayerCallback
import java.io.IOException import java.io.IOException
/** /**
* @author xufulong * The controller of MediaPlayer
* @date 3/18/22 1:53 PM *
* @desc The controller of MediaPlayer * @author frank
* @date 2022/3/18
*/ */
open class MediaPlayerController(playerCallback: PlayerCallback) { open class MediaPlayController(playerCallback: PlayerCallback) {
private var mediaPlayer: MediaPlayer? = null private var mediaPlayer: MediaPlayer? = null
private var renderFirstFrame: Boolean = false private var renderFirstFrame: Boolean = false
@ -20,7 +22,7 @@ open class MediaPlayerController(playerCallback: PlayerCallback) {
this.playerCallback = playerCallback this.playerCallback = playerCallback
} }
fun initPlayer(filePath :String, surface :Surface) { fun initPlayer(filePath: String, surface: Surface) {
if (mediaPlayer != null) { if (mediaPlayer != null) {
releasePlayer() releasePlayer()
} }
@ -31,21 +33,20 @@ open class MediaPlayerController(playerCallback: PlayerCallback) {
mediaPlayer!!.start() mediaPlayer!!.start()
playerCallback?.onPrepare() playerCallback?.onPrepare()
} }
mediaPlayer!!.setOnErrorListener { mediaPlayer!!.setOnErrorListener { mp: MediaPlayer?, what: Int, extra: Int ->
mp: MediaPlayer?, what: Int, extra: Int ->
return@setOnErrorListener playerCallback?.onError(what, extra)!! return@setOnErrorListener playerCallback?.onError(what, extra)!!
} }
mediaPlayer!!.setOnCompletionListener { mediaPlayer!!.setOnCompletionListener {
playerCallback?.onCompleteListener() playerCallback?.onCompleteListener()
} }
mediaPlayer!!.setOnInfoListener { mediaPlayer!!.setOnInfoListener { mp, what, extra ->
mp, what, extra -> ( (
if (what == MediaPlayer.MEDIA_INFO_VIDEO_RENDERING_START) { if (what == MediaPlayer.MEDIA_INFO_VIDEO_RENDERING_START) {
if (!renderFirstFrame) { if (!renderFirstFrame) {
renderFirstFrame = true renderFirstFrame = true
playerCallback?.onRenderFirstFrame() playerCallback?.onRenderFirstFrame()
} }
}) })
return@setOnInfoListener true return@setOnInfoListener true
} }
mediaPlayer!!.setDataSource(filePath) mediaPlayer!!.setDataSource(filePath)
@ -56,19 +57,19 @@ open class MediaPlayerController(playerCallback: PlayerCallback) {
} }
} }
fun currentPosition() : Int { fun currentPosition(): Int {
if (mediaPlayer == null) if (mediaPlayer == null)
return 0 return 0
return mediaPlayer!!.currentPosition return mediaPlayer!!.currentPosition
} }
fun duration() : Int { fun duration(): Int {
if (mediaPlayer == null) if (mediaPlayer == null)
return 0 return 0
return mediaPlayer!!.duration return mediaPlayer!!.duration
} }
fun seekTo(position :Int) { fun seekTo(position: Int) {
mediaPlayer?.seekTo(position) mediaPlayer?.seekTo(position)
} }
@ -83,6 +84,44 @@ open class MediaPlayerController(playerCallback: PlayerCallback) {
} }
} }
fun getVideoWidth(): Int {
return mediaPlayer!!.videoWidth
}
fun getVideoHeight(): Int {
return mediaPlayer!!.videoHeight
}
fun mute() {
mediaPlayer?.setVolume(0.0f, 0.0f)
}
fun setVolume(volume: Float) {
if (volume < 0 || volume > 1)
return
mediaPlayer?.setVolume(volume, volume)
}
/**
* Set playback rate
*/
fun setSpeed(speed: Float) {
if (speed <= 0 || speed > 8)
return
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
val params = PlaybackParams()
params.speed = speed
mediaPlayer?.playbackParams = params
}
}
/**
* Select audio or subtitle track, when there are multi tracks
*/
fun selectTrack(trackId: Int) {
mediaPlayer?.selectTrack(trackId)
}
fun releasePlayer() { fun releasePlayer() {
if (mediaPlayer != null) { if (mediaPlayer != null) {
mediaPlayer!!.stop() mediaPlayer!!.stop()

@ -10,7 +10,7 @@ import android.view.Surface
import android.view.SurfaceHolder import android.view.SurfaceHolder
import android.view.SurfaceView import android.view.SurfaceView
import android.view.View import android.view.View
import com.frank.androidmedia.controller.MediaPlayerController import com.frank.androidmedia.controller.MediaPlayController
import com.frank.androidmedia.listener.PlayerCallback import com.frank.androidmedia.listener.PlayerCallback
import com.frank.ffmpeg.R import com.frank.ffmpeg.R
@ -25,7 +25,7 @@ import com.frank.ffmpeg.handler.FFmpegHandler.MSG_TOAST
class VideoPreviewActivity : BaseActivity(), VideoPreviewBar.PreviewBarCallback, PlayerCallback { class VideoPreviewActivity : BaseActivity(), VideoPreviewBar.PreviewBarCallback, PlayerCallback {
private var playerController: MediaPlayerController? = null private var playController: MediaPlayController? = null
private var surfaceVideo: SurfaceView? = null private var surfaceVideo: SurfaceView? = null
private var videoPreviewBar: VideoPreviewBar? = null private var videoPreviewBar: VideoPreviewBar? = null
@ -34,8 +34,8 @@ class VideoPreviewActivity : BaseActivity(), VideoPreviewBar.PreviewBarCallback,
override fun handleMessage(msg: Message) { override fun handleMessage(msg: Message) {
super.handleMessage(msg) super.handleMessage(msg)
if (msg.what == MSG_UPDATE) { if (msg.what == MSG_UPDATE) {
if (videoPreviewBar != null && playerController != null) { if (videoPreviewBar != null && playController != null) {
videoPreviewBar!!.updateProgress(playerController!!.currentPosition()) videoPreviewBar!!.updateProgress(playController!!.currentPosition())
} }
this.sendEmptyMessageDelayed(MSG_UPDATE, TIME_UPDATE.toLong()) this.sendEmptyMessageDelayed(MSG_UPDATE, TIME_UPDATE.toLong())
} else if (msg.what == MSG_TOAST) { } else if (msg.what == MSG_TOAST) {
@ -52,7 +52,7 @@ class VideoPreviewActivity : BaseActivity(), VideoPreviewBar.PreviewBarCallback,
initView() initView()
mHandler.sendEmptyMessageDelayed(MSG_TOAST, 500) mHandler.sendEmptyMessageDelayed(MSG_TOAST, 500)
playerController = MediaPlayerController(this) playController = MediaPlayController(this)
} }
private fun initView() { private fun initView() {
@ -80,7 +80,7 @@ class VideoPreviewActivity : BaseActivity(), VideoPreviewBar.PreviewBarCallback,
if (surface == null || TextUtils.isEmpty(filePath)) { if (surface == null || TextUtils.isEmpty(filePath)) {
return return
} }
playerController?.initPlayer(filePath, surface) playController?.initPlayer(filePath, surface)
} }
override fun onViewClick(view: View) { override fun onViewClick(view: View) {
@ -93,14 +93,14 @@ class VideoPreviewActivity : BaseActivity(), VideoPreviewBar.PreviewBarCallback,
} }
override fun onStopTracking(progress: Long) { override fun onStopTracking(progress: Long) {
if (playerController != null) { if (playController != null) {
playerController!!.seekTo(progress.toInt()) playController!!.seekTo(progress.toInt())
} }
} }
override fun onDestroy() { override fun onDestroy() {
super.onDestroy() super.onDestroy()
playerController?.releasePlayer() playController?.releasePlayer()
if (videoPreviewBar != null) { if (videoPreviewBar != null) {
videoPreviewBar!!.release() videoPreviewBar!!.release()
} }

@ -11,16 +11,16 @@ import android.widget.SeekBar
import android.widget.TextView import android.widget.TextView
import com.frank.ffmpeg.R import com.frank.ffmpeg.R
import com.frank.ffmpeg.hardware.HardwareDecode
import com.frank.ffmpeg.util.ScreenUtil import com.frank.ffmpeg.util.ScreenUtil
import com.frank.ffmpeg.util.TimeUtil import com.frank.ffmpeg.util.TimeUtil
import com.frank.androidmedia.controller.MediaDecodeController
/** /**
* the custom view of preview SeekBar * the custom view of preview SeekBar
* Created by frank on 2019/11/16. * Created by frank on 2019/11/16.
*/ */
class VideoPreviewBar : RelativeLayout, HardwareDecode.OnDataCallback { class VideoPreviewBar : RelativeLayout, MediaDecodeController.OnDataCallback {
private var texturePreView: TextureView? = null private var texturePreView: TextureView? = null
@ -30,7 +30,7 @@ class VideoPreviewBar : RelativeLayout, HardwareDecode.OnDataCallback {
private var txtVideoDuration: TextView? = null private var txtVideoDuration: TextView? = null
private var hardwareDecode: HardwareDecode? = null private var decodeController: MediaDecodeController? = null
private var mPreviewBarCallback: PreviewBarCallback? = null private var mPreviewBarCallback: PreviewBarCallback? = null
@ -98,8 +98,8 @@ class VideoPreviewBar : RelativeLayout, HardwareDecode.OnDataCallback {
return return
} }
release() release()
hardwareDecode = HardwareDecode(surface, filePath, this) decodeController = MediaDecodeController(surface, filePath, this)
hardwareDecode!!.decode() decodeController!!.decode()
} }
private fun setListener() { private fun setListener() {
@ -109,9 +109,9 @@ class VideoPreviewBar : RelativeLayout, HardwareDecode.OnDataCallback {
return return
} }
previewBar!!.progress = progress previewBar!!.progress = progress
if (hardwareDecode != null && progress < duration) { if (decodeController != null && progress < duration) {
// us to ms // us to ms
hardwareDecode!!.seekTo((progress * 1000).toLong()) decodeController!!.seekTo((progress * 1000).toLong())
} }
val percent = progress * screenWidth / duration val percent = progress * screenWidth / duration
if (percent in (previewHalfWidth + 1) until moveEndPos && texturePreView != null) { if (percent in (previewHalfWidth + 1) until moveEndPos && texturePreView != null) {
@ -123,8 +123,8 @@ class VideoPreviewBar : RelativeLayout, HardwareDecode.OnDataCallback {
if (texturePreView != null) { if (texturePreView != null) {
texturePreView!!.visibility = View.VISIBLE texturePreView!!.visibility = View.VISIBLE
} }
if (hardwareDecode != null) { if (decodeController != null) {
hardwareDecode!!.setPreviewing(true) decodeController!!.setPreviewing(true)
} }
} }
@ -135,8 +135,8 @@ class VideoPreviewBar : RelativeLayout, HardwareDecode.OnDataCallback {
if (mPreviewBarCallback != null) { if (mPreviewBarCallback != null) {
mPreviewBarCallback!!.onStopTracking(seekBar.progress.toLong()) mPreviewBarCallback!!.onStopTracking(seekBar.progress.toLong())
} }
if (hardwareDecode != null) { if (decodeController != null) {
hardwareDecode!!.setPreviewing(false) decodeController!!.setPreviewing(false)
} }
} }
}) })
@ -179,9 +179,9 @@ class VideoPreviewBar : RelativeLayout, HardwareDecode.OnDataCallback {
} }
fun release() { fun release() {
if (hardwareDecode != null) { if (decodeController != null) {
hardwareDecode!!.release() decodeController!!.release()
hardwareDecode = null decodeController = null
} }
} }

Loading…
Cancel
Save