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
|
||||
)
|
||||
|
||||
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? {
|
||||
val codecList = MediaCodecList(MediaCodecList.REGULAR_CODECS)
|
||||
val candidates = codecList.codecInfos
|
||||
|
||||
+2
-1
@@ -11,6 +11,7 @@ import android.os.Build
|
||||
import android.util.Log
|
||||
import java.io.File
|
||||
import java.nio.ByteBuffer
|
||||
import java.util.UUID
|
||||
|
||||
class VideoCompressor(
|
||||
private val context: Context,
|
||||
@@ -72,7 +73,7 @@ class VideoCompressor(
|
||||
encoderInfo: CodecSelector.EncoderInfo,
|
||||
useHevc: Boolean
|
||||
): Map<String, Any> {
|
||||
val outputFile = File(context.cacheDir, "${System.currentTimeMillis()}.mp4")
|
||||
val outputFile = File(context.cacheDir, "${UUID.randomUUID()}.mp4")
|
||||
|
||||
var extractor: MediaExtractor? = null
|
||||
var muxer: MediaMuxer? = null
|
||||
|
||||
@@ -113,7 +113,7 @@ class VideoCompressor {
|
||||
]
|
||||
let peakBytesPerSecond = Int(Double(effectiveBitrate) / 8.0 * 1.5)
|
||||
compressionProps[kVTCompressionPropertyKey_DataRateLimits as String] = [
|
||||
peakBytesPerSecond, 1
|
||||
peakBytesPerSecond, 1.0
|
||||
] as CFArray
|
||||
|
||||
let videoColorProps: [String: Any] = [
|
||||
@@ -259,20 +259,27 @@ class VideoCompressor {
|
||||
) async {
|
||||
var lastProgressTime: CFAbsoluteTime = 0
|
||||
var lastAppendedPTS: CMTime?
|
||||
var finished = false
|
||||
|
||||
await withCheckedContinuation { (continuation: CheckedContinuation<Void, Never>) in
|
||||
writerInput.requestMediaDataWhenReady(
|
||||
on: DispatchQueue(label: "com.bsky.videocompress.video")
|
||||
) {
|
||||
while writerInput.isReadyForMoreMediaData {
|
||||
if self.isCancelled {
|
||||
let finish = {
|
||||
if !finished {
|
||||
finished = true
|
||||
writerInput.markAsFinished()
|
||||
continuation.resume()
|
||||
}
|
||||
}
|
||||
while writerInput.isReadyForMoreMediaData {
|
||||
if finished { return }
|
||||
if self.isCancelled {
|
||||
finish()
|
||||
return
|
||||
}
|
||||
guard let sampleBuffer = readerOutput.copyNextSampleBuffer() else {
|
||||
writerInput.markAsFinished()
|
||||
continuation.resume()
|
||||
finish()
|
||||
return
|
||||
}
|
||||
|
||||
@@ -286,8 +293,7 @@ class VideoCompressor {
|
||||
lastAppendedPTS = pts
|
||||
|
||||
if !writerInput.append(sampleBuffer) {
|
||||
writerInput.markAsFinished()
|
||||
continuation.resume()
|
||||
finish()
|
||||
return
|
||||
}
|
||||
|
||||
@@ -306,24 +312,31 @@ class VideoCompressor {
|
||||
readerOutput: AVAssetReaderOutput,
|
||||
writerInput: AVAssetWriterInput
|
||||
) async {
|
||||
var finished = false
|
||||
|
||||
await withCheckedContinuation { (continuation: CheckedContinuation<Void, Never>) in
|
||||
writerInput.requestMediaDataWhenReady(
|
||||
on: DispatchQueue(label: "com.bsky.videocompress.audio")
|
||||
) {
|
||||
while writerInput.isReadyForMoreMediaData {
|
||||
if self.isCancelled {
|
||||
let finish = {
|
||||
if !finished {
|
||||
finished = true
|
||||
writerInput.markAsFinished()
|
||||
continuation.resume()
|
||||
}
|
||||
}
|
||||
while writerInput.isReadyForMoreMediaData {
|
||||
if finished { return }
|
||||
if self.isCancelled {
|
||||
finish()
|
||||
return
|
||||
}
|
||||
guard let sampleBuffer = readerOutput.copyNextSampleBuffer() else {
|
||||
writerInput.markAsFinished()
|
||||
continuation.resume()
|
||||
finish()
|
||||
return
|
||||
}
|
||||
if !writerInput.append(sampleBuffer) {
|
||||
writerInput.markAsFinished()
|
||||
continuation.resume()
|
||||
finish()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user