У меня проблемы с объединением сегментов, которые поступают из живой записи через AvcaptureSession. Во время захвата avassetwritter использует свой делегат, чтобы сбрасывать сегменты на диск. Я использую эту реализацию делегата: < /p>
protocol SegmentedWriterDelegate: AnyObject, Sendable {
func onSegmentCompleted(atIndex index: UInt, url: URL)
}
final actor SegmentedWriter: NSObject, AVAssetWriterDelegate {
private let queue = DispatchSerialQueue(label: "my serial queue", qos: .utility)
nonisolated var unownedExecutor: UnownedSerialExecutor {
queue.asUnownedSerialExecutor()
}
public static var sessionDir: String {
"LastRecordingSession"
}
public static var outputDir: URL {
URL.documentsDirectory.appending(path: sessionDir, directoryHint: .isDirectory)
}
public static var fileFormat: String {
"segment_%.4i.mp4"
}
private var initializationData = Data()
private var segmentIndex: UInt = 0
private let fileManager: FileManager
private weak var delegate: SegmentedWriterDelegate?
init(fileManager: FileManager = .default) {
self.fileManager = fileManager
try? fileManager.createDirectory(at: SegmentedWriter.outputDir, withIntermediateDirectories: true)
}
func observe(using delegate: SegmentedWriterDelegate) {
self.delegate = delegate
}
nonisolated func assetWriter(
_ writer: AVAssetWriter,
didOutputSegmentData segmentData: Data,
segmentType: AVAssetSegmentType
) {
queue.sync {
assumeIsolated {
$0.onNewSegment(segmentData: segmentData, segmentType: segmentType)
}
}
}
private func onNewSegment(segmentData: Data, segmentType: AVAssetSegmentType) {
logger.debug("SR new segment \(segmentType.rawValue)")
switch segmentType {
case .initialization:
initializationData = segmentData
case .separable:
let fileName = String(format: Self.fileFormat, segmentIndex)
let fileURL = Self.outputDir.appendingPathComponent(fileName)
let mp4Data = initializationData + segmentData
try? mp4Data.write(to: fileURL)
delegate?.onSegmentCompleted(atIndex: segmentIndex, url: fileURL)
segmentIndex += 1
@unknown default:
break
}
}
}
< /code>
Таким образом, результаты являются сегментами в некотором каталоге. Теперь в конце сеанса записи мне нужно объединить все эти файлы в одну композицию и экспортировать его в один файл MP4. Я знаю, что могу записать один файл прямо из AvcaptureSession, но эти сегменты необходимы в другом процессе. На данный момент я думаю, как объединить все эти сегменты без каких -либо аудиорезинтов/сокращений, потому что время от времени решение ниже клипов при объединении точек, когда сегменты объединяются. Вот что у меня есть до сих пор .. < /p>
Логика для создания композиции < /p>
private func _run() async throws -> AVComposition {
let segments = try getSegmentURLs(from: segmentsDirectory, order: .forward)
guard segments.count > 0 else {
throw SegmentComposerError.noSegments
}
let composition = AVMutableComposition()
let options: [String: Any] = [AVURLAssetPreferPreciseDurationAndTimingKey: true]
var cursorTime: CMTime = .zero
for segmentURL in segments {
let segment: AVAsset = AVURLAsset(url: segmentURL, options: options)
let segmentDuration = try await segment.load(.duration)
print(">>> segment \(segmentURL.lastPathComponent) - duration: \(segmentDuration)")
let segmentTimeRange = CMTimeRange(start: .zero, duration: segmentDuration)
try await composition.insertTimeRange(segmentTimeRange, of: segment, at: cursorTime)
cursorTime = CMTimeAdd(cursorTime, segmentDuration)
}
return composition
}
< /code>
Чего я не уверен, что во время создания этой композиции оператор печати выводит длительность сегментов, и эти сегменты имеют разные временные рамки. Как это, например: < /p>
>>> segment segment_0000.mp4 - duration: CMTime(value: 859, timescale: 600, flags: __C.CMTimeFlags(rawValue: 1), epoch: 0)
>>> segment segment_0001.mp4 - duration: CMTime(value: 3581, timescale: 600, flags: __C.CMTimeFlags(rawValue: 1), epoch: 0)
>>> segment segment_0002.mp4 - duration: CMTime(value: 264600, timescale: 44100, flags: __C.CMTimeFlags(rawValue: 1), epoch: 0)
>>> segment segment_0003.mp4 - duration: CMTime(value: 3601, timescale: 600, flags: __C.CMTimeFlags(rawValue: 1), epoch: 0)
>>> segment segment_0004.mp4 - duration: CMTime(value: 264600, timescale: 44100, flags: __C.CMTimeFlags(rawValue: 1), epoch: 0)
>>> segment segment_0005.mp4 - duration: CMTime(value: 3601, timescale: 600, flags: __C.CMTimeFlags(rawValue: 1), epoch: 0)
>>> segment segment_0006.mp4 - duration: CMTime(value: 1180, timescale: 600, flags: __C.CMTimeFlags(rawValue: 1), epoch: 0)
< /code>
Так что иногда он составляет 600, а иногда он равен скорости дискретизации аудио. Аспиратор, который создает эти сегменты, настроен таким образом: < /p>
protocol WriterCreator: Sendable {
func createWriter(delegate: AVAssetWriterDelegate) -> AVAssetWriter
}
struct SegmentedWriterCreator: WriterCreator {
/// Default value 6 seconds
static let defaultSegmentInterval = CMTime(value: 6, timescale: 1)
private let segmentInterval: CMTime
init(segmentInterval: CMTime = SegmentedWriterCreator.defaultSegmentInterval) {
self.segmentInterval = segmentInterval
}
func createWriter(delegate: AVAssetWriterDelegate) -> AVAssetWriter {
let writer = AVAssetWriter(contentType: .mpeg4Movie)
writer.outputFileTypeProfile = .mpeg4AppleHLS
writer.preferredOutputSegmentInterval = segmentInterval
writer.initialSegmentStartTime = .zero
writer.delegate = delegate
return writer
}
}
< /code>
И вот как я пишу эту композицию на диск. < /p>
class AssetExporter: AsyncRunner {
enum AssetExporterError: Swift.Error {
case failed(reason: String)
}
private let asset: AVAsset
private let presetName: String
private var exportSession: AVAssetExportSession?
private let outputURL: URL
init(_ asset: AVComposition, toURL outputURL: URL, presetName: String = AVAssetExportPresetPassthrough) {
self.asset = asset
self.outputURL = outputURL
self.presetName = presetName
}
@discardableResult
func run() async -> Result {
do {
let url = try await _run()
return .success(url)
} catch {
return .failure(error)
}
}
private func _run() async throws -> URL {
self.exportSession = AVAssetExportSession(asset: asset, presetName: presetName)
guard let exportSession = self.exportSession else {
throw AssetExporterError.failed(reason: "Can not create export session")
}
if #available(iOS 18, *) {
try await exportSession.export(to: outputURL, as: .mp4)
} else {
exportSession.outputURL = outputURL
exportSession.outputFileType = .mp4
await exportSession.export()
}
return outputURL
}
}
< /code>
Я не уверен, как диагностировать, где проблема в этом потоке. Это во время захвата и автора активов или его входной конфигурации. Это в сегментере, который пишет эти сегменты на диск, или, может быть, в композиторе, где создается AvComposition. У меня проблема заключается в том, что окончательный выходной звук MP4 -файла является заметной обрезкой в точках слияния между образцами. Не всегда, иногда это не обрезает, но в большинстве случаев он зажигается. Любая помощь или идеи, что проверить, приветствуется. Может быть, мне следует создавать треки композиции отдельно - только видео, а затем аудио -трек?extension CMTime {
func debugString() -> String {
"(\(value)/\(timescale)) - \(seconds)"
}
}
private func _run() async throws -> AVComposition {
let segments = try getSegmentURLs(from: segmentsDirectory, order: .forward)
guard segments.count > 0 else {
throw SegmentComposerError.noSegments
}
let composition = AVMutableComposition()
let outputVideoTrack = composition
.addMutableTrack(withMediaType: .video, preferredTrackID: kCMPersistentTrackID_Invalid)
guard let outputVideoTrack else {
throw SegmentComposerError.failed(reason: "Could not add video track to composition")
}
let outputAudioTrack = composition
.addMutableTrack(withMediaType: .audio, preferredTrackID: kCMPersistentTrackID_Invalid)
guard let outputAudioTrack else {
throw SegmentComposerError.failed(reason: "Could not add audio track to composition")
}
let options: [String: Any] = [AVURLAssetPreferPreciseDurationAndTimingKey: true]
var cursorTime: CMTime = .zero
for segmentURL in segments {
let segment: AVAsset = AVURLAsset(url: segmentURL, options: options)
let (tracks, segmentDuration) = try await segment.load(.tracks, .duration)
guard let videoTrack = tracks.first(where: { $0.mediaType == .video }) else {
throw SegmentComposerError.noVideoTrack(segmentURL.lastPathComponent)
}
guard let audioTrack = tracks.first(where: { $0.mediaType == .audio }) else {
throw SegmentComposerError.noAudioTrack(segmentURL.lastPathComponent)
}
let videoTimeRange = try await videoTrack.load(.timeRange)
let audioTimeRange = try await audioTrack.load(.timeRange)
print("> \(segmentURL.lastPathComponent) - Duration: \(segmentDuration.debugString())")
print(" videoDuration: \(videoTimeRange.duration.debugString())")
print(" audioDuration: \(audioTimeRange.duration.debugString())")
try outputVideoTrack.insertTimeRange(audioTimeRange, of: videoTrack, at: cursorTime)
try outputAudioTrack.insertTimeRange(audioTimeRange, of: audioTrack, at: cursorTime)
cursorTime = CMTimeAdd(cursorTime, audioTimeRange.duration)
}
return composition
}
< /code>
Вывод в терминале: < /p>
> segment_0000.mp4 - Duration: (187/600) - 0.31166666666666665
videoDuration: (187/600) - 0.31166666666666665
audioDuration: (3008/44100) - 0.06820861678004535
> segment_0001.mp4 - Duration: (262080/44100) - 5.942857142857143
videoDuration: (3481/600) - 5.801666666666667
audioDuration: (262080/44100) - 5.942857142857143
> segment_0002.mp4 - Duration: (3600/600) - 6.0
videoDuration: (3600/600) - 6.0
audioDuration: (263104/44100) - 5.966077097505669
> segment_0003.mp4 - Duration: (3601/600) - 6.001666666666667
videoDuration: (3601/600) - 6.001666666666667
audioDuration: (262080/44100) - 5.942857142857143
> segment_0004.mp4 - Duration: (89024/44100) - 2.018684807256236
videoDuration: (1160/600) - 1.9333333333333333
audioDuration: (89024/44100) - 2.018684807256236
< /code>
Таким образом, мы видим, что продолжительность сегмента и временной шкалы выбираются на более длинной дорожке в сегменте. Я также вижу, что во всех зарегистрированных сегментах продолжительность видео -трека аудио против видео отличается, поэтому мой вопрос заключается в том, как объединить эти сегменты HLS в один файл MP4? Если я выберу Videotimerange, звук обрезает, и когда я выбираю AutiTimerange, то звук обрезается, но меньше, и иногда у меня есть странные кадра, замененные черными рамками.
Подробнее здесь: https://stackoverflow.com/questions/797 ... omposition
Правильно слияние сегментов MP4 от AvassetWritter с использованием AvmutableCoposion ⇐ IOS
Программируем под IOS
1753590740
Anonymous
У меня проблемы с объединением сегментов, которые поступают из живой записи через AvcaptureSession. Во время захвата avassetwritter использует свой делегат, чтобы сбрасывать сегменты на диск. Я использую эту реализацию делегата: < /p>
protocol SegmentedWriterDelegate: AnyObject, Sendable {
func onSegmentCompleted(atIndex index: UInt, url: URL)
}
final actor SegmentedWriter: NSObject, AVAssetWriterDelegate {
private let queue = DispatchSerialQueue(label: "my serial queue", qos: .utility)
nonisolated var unownedExecutor: UnownedSerialExecutor {
queue.asUnownedSerialExecutor()
}
public static var sessionDir: String {
"LastRecordingSession"
}
public static var outputDir: URL {
URL.documentsDirectory.appending(path: sessionDir, directoryHint: .isDirectory)
}
public static var fileFormat: String {
"segment_%.4i.mp4"
}
private var initializationData = Data()
private var segmentIndex: UInt = 0
private let fileManager: FileManager
private weak var delegate: SegmentedWriterDelegate?
init(fileManager: FileManager = .default) {
self.fileManager = fileManager
try? fileManager.createDirectory(at: SegmentedWriter.outputDir, withIntermediateDirectories: true)
}
func observe(using delegate: SegmentedWriterDelegate) {
self.delegate = delegate
}
nonisolated func assetWriter(
_ writer: AVAssetWriter,
didOutputSegmentData segmentData: Data,
segmentType: AVAssetSegmentType
) {
queue.sync {
assumeIsolated {
$0.onNewSegment(segmentData: segmentData, segmentType: segmentType)
}
}
}
private func onNewSegment(segmentData: Data, segmentType: AVAssetSegmentType) {
logger.debug("SR new segment \(segmentType.rawValue)")
switch segmentType {
case .initialization:
initializationData = segmentData
case .separable:
let fileName = String(format: Self.fileFormat, segmentIndex)
let fileURL = Self.outputDir.appendingPathComponent(fileName)
let mp4Data = initializationData + segmentData
try? mp4Data.write(to: fileURL)
delegate?.onSegmentCompleted(atIndex: segmentIndex, url: fileURL)
segmentIndex += 1
@unknown default:
break
}
}
}
< /code>
Таким образом, результаты являются сегментами в некотором каталоге. Теперь в конце сеанса записи мне нужно объединить все эти файлы в одну композицию и экспортировать его в один файл MP4. Я знаю, что могу записать один файл прямо из AvcaptureSession, но эти сегменты необходимы в другом процессе. На данный момент я думаю, как объединить все эти сегменты без каких -либо аудиорезинтов/сокращений, потому что время от времени решение ниже клипов при объединении точек, когда сегменты объединяются. Вот что у меня есть до сих пор .. < /p>
Логика для создания композиции < /p>
private func _run() async throws -> AVComposition {
let segments = try getSegmentURLs(from: segmentsDirectory, order: .forward)
guard segments.count > 0 else {
throw SegmentComposerError.noSegments
}
let composition = AVMutableComposition()
let options: [String: Any] = [AVURLAssetPreferPreciseDurationAndTimingKey: true]
var cursorTime: CMTime = .zero
for segmentURL in segments {
let segment: AVAsset = AVURLAsset(url: segmentURL, options: options)
let segmentDuration = try await segment.load(.duration)
print(">>> segment \(segmentURL.lastPathComponent) - duration: \(segmentDuration)")
let segmentTimeRange = CMTimeRange(start: .zero, duration: segmentDuration)
try await composition.insertTimeRange(segmentTimeRange, of: segment, at: cursorTime)
cursorTime = CMTimeAdd(cursorTime, segmentDuration)
}
return composition
}
< /code>
Чего я не уверен, что во время создания этой композиции оператор печати выводит длительность сегментов, и эти сегменты имеют разные временные рамки. Как это, например: < /p>
>>> segment segment_0000.mp4 - duration: CMTime(value: 859, timescale: 600, flags: __C.CMTimeFlags(rawValue: 1), epoch: 0)
>>> segment segment_0001.mp4 - duration: CMTime(value: 3581, timescale: 600, flags: __C.CMTimeFlags(rawValue: 1), epoch: 0)
>>> segment segment_0002.mp4 - duration: CMTime(value: 264600, timescale: 44100, flags: __C.CMTimeFlags(rawValue: 1), epoch: 0)
>>> segment segment_0003.mp4 - duration: CMTime(value: 3601, timescale: 600, flags: __C.CMTimeFlags(rawValue: 1), epoch: 0)
>>> segment segment_0004.mp4 - duration: CMTime(value: 264600, timescale: 44100, flags: __C.CMTimeFlags(rawValue: 1), epoch: 0)
>>> segment segment_0005.mp4 - duration: CMTime(value: 3601, timescale: 600, flags: __C.CMTimeFlags(rawValue: 1), epoch: 0)
>>> segment segment_0006.mp4 - duration: CMTime(value: 1180, timescale: 600, flags: __C.CMTimeFlags(rawValue: 1), epoch: 0)
< /code>
Так что иногда он составляет 600, а иногда он равен скорости дискретизации аудио. Аспиратор, который создает эти сегменты, настроен таким образом: < /p>
protocol WriterCreator: Sendable {
func createWriter(delegate: AVAssetWriterDelegate) -> AVAssetWriter
}
struct SegmentedWriterCreator: WriterCreator {
/// Default value 6 seconds
static let defaultSegmentInterval = CMTime(value: 6, timescale: 1)
private let segmentInterval: CMTime
init(segmentInterval: CMTime = SegmentedWriterCreator.defaultSegmentInterval) {
self.segmentInterval = segmentInterval
}
func createWriter(delegate: AVAssetWriterDelegate) -> AVAssetWriter {
let writer = AVAssetWriter(contentType: .mpeg4Movie)
writer.outputFileTypeProfile = .mpeg4AppleHLS
writer.preferredOutputSegmentInterval = segmentInterval
writer.initialSegmentStartTime = .zero
writer.delegate = delegate
return writer
}
}
< /code>
И вот как я пишу эту композицию на диск. < /p>
class AssetExporter: AsyncRunner {
enum AssetExporterError: Swift.Error {
case failed(reason: String)
}
private let asset: AVAsset
private let presetName: String
private var exportSession: AVAssetExportSession?
private let outputURL: URL
init(_ asset: AVComposition, toURL outputURL: URL, presetName: String = AVAssetExportPresetPassthrough) {
self.asset = asset
self.outputURL = outputURL
self.presetName = presetName
}
@discardableResult
func run() async -> Result {
do {
let url = try await _run()
return .success(url)
} catch {
return .failure(error)
}
}
private func _run() async throws -> URL {
self.exportSession = AVAssetExportSession(asset: asset, presetName: presetName)
guard let exportSession = self.exportSession else {
throw AssetExporterError.failed(reason: "Can not create export session")
}
if #available(iOS 18, *) {
try await exportSession.export(to: outputURL, as: .mp4)
} else {
exportSession.outputURL = outputURL
exportSession.outputFileType = .mp4
await exportSession.export()
}
return outputURL
}
}
< /code>
Я не уверен, как диагностировать, где проблема в этом потоке. Это во время захвата и автора активов или его входной конфигурации. Это в сегментере, который пишет эти сегменты на диск, или, может быть, в композиторе, где создается AvComposition. У меня проблема заключается в том, что окончательный выходной звук MP4 -файла является заметной обрезкой в точках слияния между образцами. Не всегда, иногда это не обрезает, но в большинстве случаев он зажигается. Любая помощь или идеи, что проверить, приветствуется. Может быть, мне следует создавать треки композиции отдельно - только видео, а затем аудио -трек?extension CMTime {
func debugString() -> String {
"(\(value)/\(timescale)) - \(seconds)"
}
}
private func _run() async throws -> AVComposition {
let segments = try getSegmentURLs(from: segmentsDirectory, order: .forward)
guard segments.count > 0 else {
throw SegmentComposerError.noSegments
}
let composition = AVMutableComposition()
let outputVideoTrack = composition
.addMutableTrack(withMediaType: .video, preferredTrackID: kCMPersistentTrackID_Invalid)
guard let outputVideoTrack else {
throw SegmentComposerError.failed(reason: "Could not add video track to composition")
}
let outputAudioTrack = composition
.addMutableTrack(withMediaType: .audio, preferredTrackID: kCMPersistentTrackID_Invalid)
guard let outputAudioTrack else {
throw SegmentComposerError.failed(reason: "Could not add audio track to composition")
}
let options: [String: Any] = [AVURLAssetPreferPreciseDurationAndTimingKey: true]
var cursorTime: CMTime = .zero
for segmentURL in segments {
let segment: AVAsset = AVURLAsset(url: segmentURL, options: options)
let (tracks, segmentDuration) = try await segment.load(.tracks, .duration)
guard let videoTrack = tracks.first(where: { $0.mediaType == .video }) else {
throw SegmentComposerError.noVideoTrack(segmentURL.lastPathComponent)
}
guard let audioTrack = tracks.first(where: { $0.mediaType == .audio }) else {
throw SegmentComposerError.noAudioTrack(segmentURL.lastPathComponent)
}
let videoTimeRange = try await videoTrack.load(.timeRange)
let audioTimeRange = try await audioTrack.load(.timeRange)
print("> \(segmentURL.lastPathComponent) - Duration: \(segmentDuration.debugString())")
print(" videoDuration: \(videoTimeRange.duration.debugString())")
print(" audioDuration: \(audioTimeRange.duration.debugString())")
try outputVideoTrack.insertTimeRange(audioTimeRange, of: videoTrack, at: cursorTime)
try outputAudioTrack.insertTimeRange(audioTimeRange, of: audioTrack, at: cursorTime)
cursorTime = CMTimeAdd(cursorTime, audioTimeRange.duration)
}
return composition
}
< /code>
Вывод в терминале: < /p>
> segment_0000.mp4 - Duration: (187/600) - 0.31166666666666665
videoDuration: (187/600) - 0.31166666666666665
audioDuration: (3008/44100) - 0.06820861678004535
> segment_0001.mp4 - Duration: (262080/44100) - 5.942857142857143
videoDuration: (3481/600) - 5.801666666666667
audioDuration: (262080/44100) - 5.942857142857143
> segment_0002.mp4 - Duration: (3600/600) - 6.0
videoDuration: (3600/600) - 6.0
audioDuration: (263104/44100) - 5.966077097505669
> segment_0003.mp4 - Duration: (3601/600) - 6.001666666666667
videoDuration: (3601/600) - 6.001666666666667
audioDuration: (262080/44100) - 5.942857142857143
> segment_0004.mp4 - Duration: (89024/44100) - 2.018684807256236
videoDuration: (1160/600) - 1.9333333333333333
audioDuration: (89024/44100) - 2.018684807256236
< /code>
Таким образом, мы видим, что продолжительность сегмента и временной шкалы выбираются на более длинной дорожке в сегменте. Я также вижу, что во всех зарегистрированных сегментах продолжительность видео -трека аудио против видео отличается, поэтому мой вопрос заключается в том, как объединить эти сегменты HLS в один файл MP4? Если я выберу Videotimerange, звук обрезает, и когда я выбираю AutiTimerange, то звук обрезается, но меньше, и иногда у меня есть странные кадра, замененные черными рамками.
Подробнее здесь: [url]https://stackoverflow.com/questions/79714334/properly-merge-mp4-segments-from-avassetwritter-using-avmutablecomposition[/url]
Ответить
1 сообщение
• Страница 1 из 1
Перейти
- Кемерово-IT
- ↳ Javascript
- ↳ C#
- ↳ JAVA
- ↳ Elasticsearch aggregation
- ↳ Python
- ↳ Php
- ↳ Android
- ↳ Html
- ↳ Jquery
- ↳ C++
- ↳ IOS
- ↳ CSS
- ↳ Excel
- ↳ Linux
- ↳ Apache
- ↳ MySql
- Детский мир
- Для души
- ↳ Музыкальные инструменты даром
- ↳ Печатная продукция даром
- Внешняя красота и здоровье
- ↳ Одежда и обувь для взрослых даром
- ↳ Товары для здоровья
- ↳ Физкультура и спорт
- Техника - даром!
- ↳ Автомобилистам
- ↳ Компьютерная техника
- ↳ Плиты: газовые и электрические
- ↳ Холодильники
- ↳ Стиральные машины
- ↳ Телевизоры
- ↳ Телефоны, смартфоны, плашеты
- ↳ Швейные машинки
- ↳ Прочая электроника и техника
- ↳ Фототехника
- Ремонт и интерьер
- ↳ Стройматериалы, инструмент
- ↳ Мебель и предметы интерьера даром
- ↳ Cантехника
- Другие темы
- ↳ Разное даром
- ↳ Давай меняться!
- ↳ Отдам\возьму за копеечку
- ↳ Работа и подработка в Кемерове
- ↳ Давай с тобой поговорим...
Мобильная версия