I'm having a problem with Samsung Galaxy s6 when I record a video, preview is fine but when I finish recording the video the result is a video with glitching and green flickering. I'm having this problem with this model with all phones it redord at it should be...tbh...I've tried a lot of changes and I don't know what to do
Here a video of the problem
Here the SurfaceView:
class AutoFitSurfaceView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyle: Int = 0
) : SurfaceView(context, attrs, defStyle) {
private var aspectRatio = 0f
/**
* Sets the aspect ratio for this view. The size of the view will be
* measured based on the ratio calculated from the parameters.
*
* @param width Camera resolution horizontal size
* @param height Camera resolution vertical size
*/
fun setAspectRatio(width: Int, height: Int) {
require(width > 0 && height > 0) { "Size cannot be negative" }
aspectRatio = width.toFloat() / height.toFloat()
holder.setFixedSize(width, height)
requestLayout()
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
val width = MeasureSpec.getSize(widthMeasureSpec)
val height = MeasureSpec.getSize(heightMeasureSpec)
if (aspectRatio == 0f) {
setMeasuredDimension(width, height)
} else {
// Performs height-crop transformation of the camera frames if necessary
val newHeight: Int
val actualRatio = 1f / aspectRatio
newHeight = (width / actualRatio).roundToInt()
setMeasuredDimension(width, newHeight)
}
}
}
This is the implementation:
class VideoCameraImplCamera2(
private val context: Context,
private val lifecycleOwner: LifecycleOwner,
private val viewFinder: AutoFitSurfaceView,
private val videoDirectory: String,
private val resolution: Resolution,
override val listener: VideoCamera.VideoCameraListener
) : VideoCamera, LifecycleObserver {
enum class CameraLens(val value: Int) {
FRONT(1),
BACK(0)
}
companion object {
private const val RECORDER_VIDEO_BITRATE: Int = 10_000_000
private const val FRAME_PER_SECOND: Int = 30
private const val RECORDER_AUDIO_BITRATE: Int = 192_000
private const val RECORDER_AUDIO_SAMPLING_RATE: Int = 44_100
private const val DEVICE_ROTATION = Surface.ROTATION_0
private const val SENSOR_ORIENTATION_DEFAULT_DEGREES = 90
private const val SENSOR_ORIENTATION_INVERSE_DEGREES = 270
private val DEFAULT_ORIENTATIONS = SparseIntArray().apply {
append(Surface.ROTATION_0, 90)
append(Surface.ROTATION_90, 0)
append(Surface.ROTATION_180, 270)
append(Surface.ROTATION_270, 180)
}
private val INVERSE_ORIENTATIONS = SparseIntArray().apply {
append(Surface.ROTATION_0, 270)
append(Surface.ROTATION_90, 180)
append(Surface.ROTATION_180, 90)
append(Surface.ROTATION_270, 0)
}
}
/**
* Orientation of the camera sensor
*/
private var sensorOrientation = 0
/**
* Camera lens active
*/
private var cameraLens = CameraLens.FRONT
/**
* Whether the flash is active
*/
private var cameraFlash = VideoCamera.FlashMode.OFF
/**
* Min recording time for each video
*/
private var minRecordingTimeCompleted = true
/**
* Whether the video recording stopped before min time
*/
private var stoppedBeforeMinTimeCompleted = false
/**
* Whether the app is showing that is recording
*/
private var isRecording = false
/**
* Whether the app is recording video now
*/
private var isRecordingVideo = false
/**
* Whether the camera is available
*/
private var isAvailable = false
/**
* Minimum recording duration countdown
*/
private val minDurationCountDownTimer =
object : CountDownTimer(VideoCamera.MIN_RECORD_DURATION, 100) {
init {
minRecordingTimeCompleted = false
}
override fun onFinish() {
if (stoppedBeforeMinTimeCompleted && isRecording) {
Timber.d("Minimum recording time completed")
stopInternalRecording()
}
minRecordingTimeCompleted = true
}
override fun onTick(millisUntilFinished: Long) {
}
}
/**
* Camera id based in [cameraLens] value
*/
private val cameraId: String
get() = cameraManager.cameraIdList[cameraLens.value]
/** Detects, characterizes, and connects to a CameraDevice (used for all camera operations) */
private val cameraManager: CameraManager by lazy {
val context = context.applicationContext
context.getSystemService(Context.CAMERA_SERVICE) as CameraManager
}
/** [CameraCharacteristics] corresponding to the provided Camera ID */
private val characteristics: CameraCharacteristics
get() = cameraManager.getCameraCharacteristics(cameraId)
/**
* Setup a persistent [Surface] for the recorder so we can use it as an output target for the
* camera session without preparing the recorder
*/
private lateinit var recorderSurface: Surface
/** Saves the video recording */
private lateinit var recorder: MediaRecorder
/** [HandlerThread] where all camera operations run */
private val cameraThread = HandlerThread("CameraThread").apply { start() }
/** [Handler] corresponding to [cameraThread] */
private val cameraHandler = Handler(cameraThread.looper)
/** Captures frames from a [CameraDevice] for our video recording */
private lateinit var session: CameraCaptureSession
/** The [CameraDevice] that will be opened in this fragment */
private lateinit var camera: CameraDevice
/** Requests used for preview only in the [CameraCaptureSession] */
private lateinit var previewRequest: CaptureRequest.Builder
/** Requests used for preview and recording in the [CameraCaptureSession] */
private lateinit var recordRequest: CaptureRequest.Builder
private var recordingStartMillis: Long = 0L
/** Number or requested recording this session*/
private var recordingRequested = 0
/** Last clip duration */
private var lastRecordingDuration = 0F
/** Indicate whether the camera is initialized */
private var initialized = false
/** Whether the camera is in pause */
private var onPause = true
/** The [android.util.Size] of video recording. */
private lateinit var videoSize: Size
/** Absolute path of recording video file. It will be create a new one in each recording */
private lateinit var videoFilePath: String
init {
viewFinder.holder.addCallback(object : SurfaceHolder.Callback {
override fun surfaceDestroyed(holder: SurfaceHolder) = Unit
override fun surfaceChanged(
holder: SurfaceHolder,
format: Int,
width: Int,
height: Int
) = Unit
override fun surfaceCreated(holder: SurfaceHolder) {
// Selects appropriate preview size and configures view finder
val previewSize = chooseVideoSize()
Timber.d("View finder size: ${viewFinder.width} x ${viewFinder.height}")
Timber.d("Selected preview size: $previewSize")
viewFinder.setAspectRatio(previewSize.width, previewSize.height)
}
})
}
/**
* Initialize video camera
*/
override fun initialize() {
initialized = true
onPause = false
viewFinder.post { initializeCamera() }
}
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
fun onResume() {
if (onPause) {
initialize()
}
}
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
fun onStop() {
onPause = true
try {
camera.close()
} catch (exc: Throwable) {
Timber.e(exc, "Error closing camera")
}
}
@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
fun onDestroy() {
recorderSurface.release()
cameraThread.quitSafely()
recorderSurface.release()
}
private fun createRecorderSurface(width: Int, height: Int): Surface {
// Get a persistent Surface from MediaCodec, don't forget to release when done
val surface = MediaCodec.createPersistentInputSurface()
// Prepare and release a dummy MediaRecorder with our new surface
// Required to allocate an appropriately sized buffer before passing the Surface as the
// output target to the capture session
createRecorder(
surface,
File(context.cacheDir, "dummy.mp4").absolutePath,
width,
height
).apply {
prepare()
release()
}
return surface
}
/** Creates a [MediaRecorder] instance using the provided [Surface] as input */
private fun createRecorder(
surface: Surface,
absoluteFilePath: String,
width: Int,
height: Int
) = MediaRecorder().apply {
setAudioSource(MediaRecorder.AudioSource.MIC)
setVideoSource(MediaRecorder.VideoSource.SURFACE)
setOutputFormat(MediaRecorder.OutputFormat.MPEG_4)
setOutputFile(absoluteFilePath)
setVideoEncodingBitRate(RECORDER_VIDEO_BITRATE)
if (FRAME_PER_SECOND > 0) setVideoFrameRate(FRAME_PER_SECOND)
setVideoSize(width, height)
setVideoEncoder(MediaRecorder.VideoEncoder.H264)
setAudioEncoder(MediaRecorder.AudioEncoder.AAC)
setAudioEncodingBitRate(RECORDER_AUDIO_BITRATE)
setAudioSamplingRate(RECORDER_AUDIO_SAMPLING_RATE)
setInputSurface(surface)
}
/** Creates a [CaptureRequest.Builder] instance for preview using [session] variable*/
private fun createPreviewRequest(): CaptureRequest.Builder {
return session.device.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW).apply {
// Add the preview surface target
addTarget(viewFinder.holder.surface)
}
}
/** Creates a [CaptureRequest.Builder] instance for recording using [session] variable*/
private fun createRecordRequest(): CaptureRequest.Builder {
// Capture request holds references to target surfaces
return session.device.createCaptureRequest(CameraDevice.TEMPLATE_RECORD).apply {
// Add the preview and recording surface targets
addTarget(viewFinder.holder.surface)
addTarget(recorderSurface)
// Sets user requested FPS for all targets
set(
CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE,
Range(FRAME_PER_SECOND, FRAME_PER_SECOND)
)
}
}
/**
* Begin all camera operations in a coroutine in the main thread. This function:
* - Opens the camera
* - Configures the camera session
* - Starts the preview by dispatching a repeating request