Address grill follow-ups: dead code, UUID filename, continuation guard, DataRateLimits
N1: Delete unused CodecSelector.selectEncoder. VideoCompressor calls findEncoder directly; selectEncoder was dead since the initial commit. N2: Switch Android output filename from System.currentTimeMillis() to UUID.randomUUID(). Eliminates the collision risk when two compressions start in the same millisecond. N3: Guard CheckedContinuation against double-resume in both processVideoTrack and processAudioTrack. AVAssetWriter shouldn't re-invoke the requestMediaDataWhenReady block after markAsFinished, but if it did, the previous code would crash via CheckedContinuation's double-resume detection. Added a `finished` flag plus a local `finish` closure that no-ops on second call. N4: DataRateLimits window duration changed from Int `1` to Double `1.0`. VideoToolbox expects the duration value as a CFNumber with float semantics; Int bridges to NSNumber(int) which works on most iOS versions but is not spec-correct.
This commit is contained in:
-9
@@ -23,15 +23,6 @@ object CodecSelector {
|
|||||||
val isHardware: Boolean
|
val isHardware: Boolean
|
||||||
)
|
)
|
||||||
|
|
||||||
fun selectEncoder(preferHevc: Boolean): EncoderInfo? {
|
|
||||||
if (preferHevc) {
|
|
||||||
val hevc = findEncoder(MediaFormat.MIMETYPE_VIDEO_HEVC, requireHardware = true)
|
|
||||||
if (hevc != null) return hevc
|
|
||||||
}
|
|
||||||
findEncoder(MediaFormat.MIMETYPE_VIDEO_AVC, requireHardware = true)?.let { return it }
|
|
||||||
return findEncoder(MediaFormat.MIMETYPE_VIDEO_AVC, requireHardware = false)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun findEncoder(mime: String, requireHardware: Boolean): EncoderInfo? {
|
fun findEncoder(mime: String, requireHardware: Boolean): EncoderInfo? {
|
||||||
val codecList = MediaCodecList(MediaCodecList.REGULAR_CODECS)
|
val codecList = MediaCodecList(MediaCodecList.REGULAR_CODECS)
|
||||||
val candidates = codecList.codecInfos
|
val candidates = codecList.codecInfos
|
||||||
|
|||||||
+2
-1
@@ -11,6 +11,7 @@ import android.os.Build
|
|||||||
import android.util.Log
|
import android.util.Log
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.nio.ByteBuffer
|
import java.nio.ByteBuffer
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
class VideoCompressor(
|
class VideoCompressor(
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
@@ -72,7 +73,7 @@ class VideoCompressor(
|
|||||||
encoderInfo: CodecSelector.EncoderInfo,
|
encoderInfo: CodecSelector.EncoderInfo,
|
||||||
useHevc: Boolean
|
useHevc: Boolean
|
||||||
): Map<String, Any> {
|
): Map<String, Any> {
|
||||||
val outputFile = File(context.cacheDir, "${System.currentTimeMillis()}.mp4")
|
val outputFile = File(context.cacheDir, "${UUID.randomUUID()}.mp4")
|
||||||
|
|
||||||
var extractor: MediaExtractor? = null
|
var extractor: MediaExtractor? = null
|
||||||
var muxer: MediaMuxer? = null
|
var muxer: MediaMuxer? = null
|
||||||
|
|||||||
@@ -113,7 +113,7 @@ class VideoCompressor {
|
|||||||
]
|
]
|
||||||
let peakBytesPerSecond = Int(Double(effectiveBitrate) / 8.0 * 1.5)
|
let peakBytesPerSecond = Int(Double(effectiveBitrate) / 8.0 * 1.5)
|
||||||
compressionProps[kVTCompressionPropertyKey_DataRateLimits as String] = [
|
compressionProps[kVTCompressionPropertyKey_DataRateLimits as String] = [
|
||||||
peakBytesPerSecond, 1
|
peakBytesPerSecond, 1.0
|
||||||
] as CFArray
|
] as CFArray
|
||||||
|
|
||||||
let videoColorProps: [String: Any] = [
|
let videoColorProps: [String: Any] = [
|
||||||
@@ -259,20 +259,27 @@ class VideoCompressor {
|
|||||||
) async {
|
) async {
|
||||||
var lastProgressTime: CFAbsoluteTime = 0
|
var lastProgressTime: CFAbsoluteTime = 0
|
||||||
var lastAppendedPTS: CMTime?
|
var lastAppendedPTS: CMTime?
|
||||||
|
var finished = false
|
||||||
|
|
||||||
await withCheckedContinuation { (continuation: CheckedContinuation<Void, Never>) in
|
await withCheckedContinuation { (continuation: CheckedContinuation<Void, Never>) in
|
||||||
writerInput.requestMediaDataWhenReady(
|
writerInput.requestMediaDataWhenReady(
|
||||||
on: DispatchQueue(label: "com.bsky.videocompress.video")
|
on: DispatchQueue(label: "com.bsky.videocompress.video")
|
||||||
) {
|
) {
|
||||||
while writerInput.isReadyForMoreMediaData {
|
let finish = {
|
||||||
if self.isCancelled {
|
if !finished {
|
||||||
|
finished = true
|
||||||
writerInput.markAsFinished()
|
writerInput.markAsFinished()
|
||||||
continuation.resume()
|
continuation.resume()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while writerInput.isReadyForMoreMediaData {
|
||||||
|
if finished { return }
|
||||||
|
if self.isCancelled {
|
||||||
|
finish()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
guard let sampleBuffer = readerOutput.copyNextSampleBuffer() else {
|
guard let sampleBuffer = readerOutput.copyNextSampleBuffer() else {
|
||||||
writerInput.markAsFinished()
|
finish()
|
||||||
continuation.resume()
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -286,8 +293,7 @@ class VideoCompressor {
|
|||||||
lastAppendedPTS = pts
|
lastAppendedPTS = pts
|
||||||
|
|
||||||
if !writerInput.append(sampleBuffer) {
|
if !writerInput.append(sampleBuffer) {
|
||||||
writerInput.markAsFinished()
|
finish()
|
||||||
continuation.resume()
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -306,24 +312,31 @@ class VideoCompressor {
|
|||||||
readerOutput: AVAssetReaderOutput,
|
readerOutput: AVAssetReaderOutput,
|
||||||
writerInput: AVAssetWriterInput
|
writerInput: AVAssetWriterInput
|
||||||
) async {
|
) async {
|
||||||
|
var finished = false
|
||||||
|
|
||||||
await withCheckedContinuation { (continuation: CheckedContinuation<Void, Never>) in
|
await withCheckedContinuation { (continuation: CheckedContinuation<Void, Never>) in
|
||||||
writerInput.requestMediaDataWhenReady(
|
writerInput.requestMediaDataWhenReady(
|
||||||
on: DispatchQueue(label: "com.bsky.videocompress.audio")
|
on: DispatchQueue(label: "com.bsky.videocompress.audio")
|
||||||
) {
|
) {
|
||||||
while writerInput.isReadyForMoreMediaData {
|
let finish = {
|
||||||
if self.isCancelled {
|
if !finished {
|
||||||
|
finished = true
|
||||||
writerInput.markAsFinished()
|
writerInput.markAsFinished()
|
||||||
continuation.resume()
|
continuation.resume()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while writerInput.isReadyForMoreMediaData {
|
||||||
|
if finished { return }
|
||||||
|
if self.isCancelled {
|
||||||
|
finish()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
guard let sampleBuffer = readerOutput.copyNextSampleBuffer() else {
|
guard let sampleBuffer = readerOutput.copyNextSampleBuffer() else {
|
||||||
writerInput.markAsFinished()
|
finish()
|
||||||
continuation.resume()
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !writerInput.append(sampleBuffer) {
|
if !writerInput.append(sampleBuffer) {
|
||||||
writerInput.markAsFinished()
|
finish()
|
||||||
continuation.resume()
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user