mirror of
https://github.com/libretro/RetroArch.git
synced 2026-11-07 08:36:02 -10:00
record: correct AVFoundation AAC queries and append failures
Use the format-ID specifier required by AudioToolbox when selecting AAC sample rates. Validate requested audio settings and retry with the encoder default bitrate when the preset is rejected. Report refused or failed audio/video appends instead of returning success. Cover the property ABI, buffer release and writer states with portable regressions, and add a native AudioToolbox contract check.
This commit is contained in:
1 parent
99df72ef0a
commit
66977c726a
8 files changed
+316
-27
No files matched your search
@@ -8,6 +8,7 @@ on:
|
||||
- 'libretro-common/formats/h265/rh265_sps_crop.c'
|
||||
- 'libretro-common/include/formats/rh265_sps_crop.h'
|
||||
- 'record/drivers/record_metal.*'
|
||||
- 'record/drivers/record_avfoundation*'
|
||||
- 'record/record_driver.*'
|
||||
- 'gfx/drivers/metal.m'
|
||||
- 'Makefile.common'
|
||||
@@ -32,6 +33,7 @@ on:
|
||||
- 'libretro-common/formats/h265/rh265_sps_crop.c'
|
||||
- 'libretro-common/include/formats/rh265_sps_crop.h'
|
||||
- 'record/drivers/record_metal.*'
|
||||
- 'record/drivers/record_avfoundation*'
|
||||
- 'record/record_driver.*'
|
||||
- 'gfx/drivers/metal.m'
|
||||
- 'Makefile.common'
|
||||
@@ -66,6 +68,8 @@ jobs:
|
||||
- name: Portable ownership and failure contracts
|
||||
working-directory: samples/record/videotoolbox_encode
|
||||
run: make check thread_test worker_thread_test audio_thread_test av_session_thread_test && ./thread_test && ./worker_thread_test && ./audio_thread_test && ./av_session_thread_test
|
||||
- name: Native AAC property ABI and advertised rates
|
||||
run: make -C samples/record/videotoolbox_encode native-audio-check
|
||||
- name: Native hardware encoder and independent pixels
|
||||
run: |
|
||||
rc=0
|
||||
|
||||
@@ -292,6 +292,23 @@ static void *avfoundation_record_init(const struct record_params *params)
|
||||
AVEncoderBitRateKey: @(audioBitRate)
|
||||
};
|
||||
|
||||
if (![handle->assetWriter canApplyOutputSettings:audioSettings
|
||||
forMediaType:AVMediaTypeAudio])
|
||||
{
|
||||
NSMutableDictionary *fallback =
|
||||
[NSMutableDictionary dictionaryWithDictionary:audioSettings];
|
||||
[fallback removeObjectForKey:AVEncoderBitRateKey];
|
||||
audioSettings = fallback;
|
||||
if (![handle->assetWriter canApplyOutputSettings:audioSettings
|
||||
forMediaType:AVMediaTypeAudio])
|
||||
{
|
||||
RARCH_ERR("[AVFoundation] Unsupported AAC output settings\n");
|
||||
avfoundation_release_handle(handle);
|
||||
return NULL;
|
||||
}
|
||||
RARCH_WARN("[AVFoundation] AAC bitrate preset unsupported; using encoder default\n");
|
||||
}
|
||||
|
||||
/* Create audio input */
|
||||
handle->audioInput = [[AVAssetWriterInput alloc]
|
||||
initWithMediaType:AVMediaTypeAudio
|
||||
@@ -324,7 +341,7 @@ static void *avfoundation_record_init(const struct record_params *params)
|
||||
}
|
||||
|
||||
RARCH_LOG("[AVFoundation] Initialized recording to %s (%ux%u @ %.2f fps, "
|
||||
"video %u kbps, audio %u kbps)\n",
|
||||
"video %u kbps, audio preset %u kbps)\n",
|
||||
params->filename, handle->width, handle->height,
|
||||
handle->fps, videoBitRate / 1000, audioBitRate / 1000);
|
||||
}
|
||||
@@ -378,6 +395,7 @@ static bool avfoundation_record_push_video(void *data,
|
||||
record_avfoundation_t *handle = (record_avfoundation_t*)data;
|
||||
CVPixelBufferRef pixelBuffer = NULL;
|
||||
CVReturn result;
|
||||
__block bool success = false;
|
||||
|
||||
if (!handle || !video_data)
|
||||
return false;
|
||||
@@ -527,15 +545,16 @@ static bool avfoundation_record_push_video(void *data,
|
||||
|
||||
/* Append pixel buffer on encoding queue */
|
||||
dispatch_sync(handle->encodingQueue, ^{
|
||||
if (handle->videoInput.readyForMoreMediaData)
|
||||
[handle->pixelBufferAdaptor appendPixelBuffer:pixelBuffer
|
||||
if (handle->assetWriter.status == AVAssetWriterStatusWriting
|
||||
&& handle->videoInput.readyForMoreMediaData)
|
||||
success = [handle->pixelBufferAdaptor appendPixelBuffer:pixelBuffer
|
||||
withPresentationTime:presentationTime];
|
||||
});
|
||||
|
||||
CVPixelBufferRelease(pixelBuffer);
|
||||
}
|
||||
|
||||
return true;
|
||||
return success;
|
||||
}
|
||||
|
||||
static bool avfoundation_record_push_audio(void *data,
|
||||
@@ -546,6 +565,7 @@ static bool avfoundation_record_push_audio(void *data,
|
||||
CMBlockBufferRef blockBuffer = NULL;
|
||||
CMFormatDescriptionRef formatDesc = NULL;
|
||||
OSStatus status;
|
||||
__block bool success = false;
|
||||
|
||||
if (!handle || !handle->audioInput || !audio_data || !audio_data->data)
|
||||
return false;
|
||||
@@ -622,14 +642,15 @@ static bool avfoundation_record_push_audio(void *data,
|
||||
|
||||
/* Append on encoding queue */
|
||||
dispatch_sync(handle->encodingQueue, ^{
|
||||
if (handle->audioInput.readyForMoreMediaData)
|
||||
[handle->audioInput appendSampleBuffer:sampleBuffer];
|
||||
if (handle->assetWriter.status == AVAssetWriterStatusWriting
|
||||
&& handle->audioInput.readyForMoreMediaData)
|
||||
success = [handle->audioInput appendSampleBuffer:sampleBuffer];
|
||||
});
|
||||
|
||||
CFRelease(sampleBuffer);
|
||||
}
|
||||
|
||||
return true;
|
||||
return success;
|
||||
}
|
||||
|
||||
static bool avfoundation_record_finalize(void *data)
|
||||
|
||||
@@ -8,28 +8,18 @@
|
||||
static double avfoundation_nearest_aac_sample_rate(double rate)
|
||||
{
|
||||
AudioFormatPropertyID prop = kAudioFormatProperty_AvailableEncodeSampleRates;
|
||||
AudioStreamBasicDescription desc;
|
||||
UInt32 format = kAudioFormatMPEG4AAC;
|
||||
UInt32 size = 0;
|
||||
OSStatus status;
|
||||
|
||||
desc.mFormatID = kAudioFormatMPEG4AAC;
|
||||
desc.mSampleRate = 0;
|
||||
desc.mFormatFlags = 0;
|
||||
desc.mBytesPerPacket = 0;
|
||||
desc.mFramesPerPacket = 0;
|
||||
desc.mBytesPerFrame = 0;
|
||||
desc.mChannelsPerFrame = 0;
|
||||
desc.mBitsPerChannel = 0;
|
||||
desc.mReserved = 0;
|
||||
|
||||
status = AudioFormatGetPropertyInfo(prop, sizeof(desc), &desc, &size);
|
||||
status = AudioFormatGetPropertyInfo(prop, sizeof(format), &format, &size);
|
||||
if (status == noErr && size > 0)
|
||||
{
|
||||
UInt32 capacity = size;
|
||||
AudioValueRange *ranges = (AudioValueRange*)malloc(size);
|
||||
if (ranges)
|
||||
{
|
||||
status = AudioFormatGetProperty(prop, sizeof(desc), &desc,
|
||||
status = AudioFormatGetProperty(prop, sizeof(format), &format,
|
||||
&size, ranges);
|
||||
if (status == noErr && size <= capacity && !(size % sizeof(AudioValueRange)))
|
||||
{
|
||||
|
||||
@@ -9,7 +9,7 @@ ifneq ($(SANITIZER),)
|
||||
CFLAGS += -fsanitize=$(SANITIZER) -fno-omit-frame-pointer
|
||||
LDFLAGS += -fsanitize=$(SANITIZER)
|
||||
endif
|
||||
all: session_gpu_refusal_test$(EXE) av_session_test$(EXE) crop_test$(EXE) av_worker_test$(EXE) audio_queue_test$(EXE) slots_test$(EXE) session_test$(EXE) worker_test$(EXE) audio_rate_test$(EXE)
|
||||
all: session_gpu_refusal_test$(EXE) av_session_test$(EXE) crop_test$(EXE) av_worker_test$(EXE) audio_queue_test$(EXE) slots_test$(EXE) session_test$(EXE) worker_test$(EXE) audio_rate_test$(EXE) audio_rate_native_test$(EXE)
|
||||
av_session_thread_test: av_session_thread_test.c $(RA)/record/record_avfoundation_session.c
|
||||
$(CC) $(CFLAGS) -pthread -o $@ av_session_thread_test.c $(RA)/record/record_avfoundation_session.c $(RA)/record/record_avfoundation_worker.c $(RA)/record/record_gpu_worker_vt.c $(RA)/record/record_gpu_queue.c $(RA)/record/record_audio_queue.c $(RA)/libretro-common/queues/retro_spsc.c $(LDFLAGS)
|
||||
av_session_test$(EXE): av_worker_test.c $(RA)/record/record_avfoundation_session.c $(RA)/record/record_avfoundation_session.h
|
||||
@@ -22,6 +22,10 @@ audio_thread_test: audio_queue_test.c $(RA)/record/record_audio_queue.c $(RA)/re
|
||||
$(CC) $(CFLAGS) -DRECORD_AUDIO_THREAD_TEST -pthread -o $@ audio_queue_test.c $(RA)/record/record_audio_queue.c $(RA)/libretro-common/queues/retro_spsc.c $(LDFLAGS)
|
||||
audio_rate_test$(EXE): audio_rate_test.c mock/AudioToolbox/AudioToolbox.h $(RA)/record/drivers/record_avfoundation_audio.h
|
||||
$(CC) $(CFLAGS) -Imock -o $@ $< $(LDFLAGS)
|
||||
audio_rate_native_test$(EXE): audio_rate_native_test.c $(RA)/record/drivers/record_avfoundation_audio.h
|
||||
$(CC) $(CFLAGS) -o $@ $< $(LDFLAGS) $(if $(filter Darwin,$(shell uname -s)),-framework AudioToolbox,)
|
||||
native-audio-check: audio_rate_native_test$(EXE)
|
||||
$(RUNNER) ./audio_rate_native_test$(EXE)
|
||||
crop_test$(EXE): crop_test.c $(RA)/libretro-common/formats/h265/rh265_sps_crop.c $(RA)/libretro-common/include/formats/rh265_sps_crop.h
|
||||
$(CC) $(CFLAGS) -o $@ $< $(LDFLAGS)
|
||||
worker_test$(EXE): worker_test.c $(RA)/record/record_gpu_worker_vt.c $(RA)/record/record_gpu_worker_vt.h $(RA)/record/record_gpu_queue.c
|
||||
@@ -36,7 +40,7 @@ thread_test: slots_test.c $(RA)/gfx/common/videotoolbox_encode_slots.h
|
||||
$(CC) $(CFLAGS) -DVT_ENCODE_THREAD_TEST -pthread -o $@ $< $(LDFLAGS)
|
||||
worker_thread_test: worker_thread_test.c $(RA)/record/record_gpu_worker_vt.c $(RA)/record/record_gpu_worker_vt.h $(RA)/record/record_gpu_queue.c
|
||||
$(CC) $(CFLAGS) -pthread -o $@ worker_thread_test.c $(RA)/record/record_gpu_worker_vt.c $(RA)/record/record_gpu_queue.c $(RA)/libretro-common/queues/retro_spsc.c $(LDFLAGS)
|
||||
check: all
|
||||
check: all legacy-check
|
||||
$(RUNNER) ./session_gpu_refusal_test$(EXE)
|
||||
$(RUNNER) ./av_session_test$(EXE)
|
||||
$(RUNNER) ./crop_test$(EXE)
|
||||
@@ -54,4 +58,7 @@ clean:
|
||||
rm -f audio_queue_test audio_queue_test.exe audio_thread_test
|
||||
rm -f slots_test slots_test.exe session_test session_test.exe worker_test worker_test.exe thread_test worker_thread_test
|
||||
rm -f audio_rate_test audio_rate_test.exe
|
||||
.PHONY: all check clean
|
||||
rm -f audio_rate_native_test audio_rate_native_test.exe
|
||||
legacy-check: legacy_append_run.py legacy_append_test.c $(RA)/record/drivers/record_avfoundation.m
|
||||
CC="$(CC)" CFLAGS="$(CFLAGS)" LDFLAGS="$(LDFLAGS)" RUNNER="$(RUNNER)" python3 legacy_append_run.py
|
||||
.PHONY: all check clean legacy-check native-audio-check
|
||||
@@ -0,0 +1,70 @@
|
||||
/* Copyright (C) 2026 The RetroArch team. SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
#include <stdio.h>
|
||||
#ifdef __APPLE__
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
#include <AudioToolbox/AudioToolbox.h>
|
||||
|
||||
static unsigned info_calls, value_calls;
|
||||
static OSStatus checked_info(AudioFormatPropertyID property, UInt32 bytes,
|
||||
const void *input, UInt32 *size)
|
||||
{
|
||||
assert(property == kAudioFormatProperty_AvailableEncodeSampleRates);
|
||||
assert(bytes == sizeof(UInt32) && input
|
||||
&& *(const UInt32*)input == kAudioFormatMPEG4AAC);
|
||||
info_calls++;
|
||||
return AudioFormatGetPropertyInfo(property, bytes, input, size);
|
||||
}
|
||||
static OSStatus checked_value(AudioFormatPropertyID property, UInt32 bytes,
|
||||
const void *input, UInt32 *size, void *output)
|
||||
{
|
||||
assert(property == kAudioFormatProperty_AvailableEncodeSampleRates);
|
||||
assert(bytes == sizeof(UInt32) && input
|
||||
&& *(const UInt32*)input == kAudioFormatMPEG4AAC);
|
||||
value_calls++;
|
||||
return AudioFormatGetProperty(property, bytes, input, size, output);
|
||||
}
|
||||
#define AudioFormatGetPropertyInfo checked_info
|
||||
#define AudioFormatGetProperty checked_value
|
||||
#include "../../../record/drivers/record_avfoundation_audio.h"
|
||||
#undef AudioFormatGetPropertyInfo
|
||||
#undef AudioFormatGetProperty
|
||||
|
||||
int main(void)
|
||||
{
|
||||
static const double inputs[] = {8000, 22050, 44100, 48000};
|
||||
UInt32 format = kAudioFormatMPEG4AAC;
|
||||
UInt32 size = 0, capacity;
|
||||
AudioValueRange *ranges;
|
||||
unsigned i, j;
|
||||
assert(!AudioFormatGetPropertyInfo(
|
||||
kAudioFormatProperty_AvailableEncodeSampleRates,
|
||||
sizeof(format), &format, &size));
|
||||
assert(size && !(size % sizeof(*ranges)));
|
||||
capacity = size;
|
||||
ranges = (AudioValueRange*)malloc(size);
|
||||
assert(ranges);
|
||||
assert(!AudioFormatGetProperty(kAudioFormatProperty_AvailableEncodeSampleRates,
|
||||
sizeof(format), &format, &size, ranges));
|
||||
assert(size && size <= capacity && !(size % sizeof(*ranges)));
|
||||
for (i = 0; i < sizeof(inputs) / sizeof(inputs[0]); i++)
|
||||
{
|
||||
double rate = avfoundation_nearest_aac_sample_rate(inputs[i]);
|
||||
int found = 0;
|
||||
for (j = 0; j < size / sizeof(*ranges); j++)
|
||||
if (rate >= ranges[j].mMinimum && rate <= ranges[j].mMaximum)
|
||||
found = 1;
|
||||
assert(found && info_calls == i + 1 && value_calls == i + 1);
|
||||
printf("AAC source %.0f Hz selects advertised output %.0f Hz\n", inputs[i], rate);
|
||||
}
|
||||
free(ranges);
|
||||
puts("PASS: native AudioToolbox property ABI and advertised AAC rates; no encode claim");
|
||||
return 0;
|
||||
}
|
||||
#else
|
||||
int main(void)
|
||||
{
|
||||
puts("UNAVAILABLE: native AudioToolbox requires Apple");
|
||||
return 77;
|
||||
}
|
||||
#endif
|
||||
@@ -8,14 +8,16 @@ static unsigned mode,allocation_fail;
|
||||
static AudioValueRange rates[2]={{32000,32000},{48000,48000}};
|
||||
OSStatus AudioFormatGetPropertyInfo(AudioFormatPropertyID property,UInt32 bytes,const void *input,UInt32 *size)
|
||||
{
|
||||
const AudioStreamBasicDescription *desc=(const AudioStreamBasicDescription*)input;
|
||||
assert(property==1 && bytes==sizeof(*desc) && desc->mFormatID==2
|
||||
&& !desc->mReserved && !desc->mSampleRate);
|
||||
assert(property==kAudioFormatProperty_AvailableEncodeSampleRates
|
||||
&& bytes==sizeof(UInt32) && input
|
||||
&& *(const UInt32*)input==kAudioFormatMPEG4AAC);
|
||||
*size=sizeof(rates); return mode==1?-1:0;
|
||||
}
|
||||
OSStatus AudioFormatGetProperty(AudioFormatPropertyID property,UInt32 bytes,const void *input,UInt32 *size,void *output)
|
||||
{
|
||||
(void)property; (void)bytes; (void)input;
|
||||
assert(property==kAudioFormatProperty_AvailableEncodeSampleRates
|
||||
&& bytes==sizeof(UInt32) && input
|
||||
&& *(const UInt32*)input==kAudioFormatMPEG4AAC);
|
||||
assert(*size==sizeof(rates));
|
||||
if (mode==3) return -1;
|
||||
memcpy(output,rates,mode==2?sizeof(rates[0]):sizeof(rates));
|
||||
@@ -40,6 +42,11 @@ int main(void)
|
||||
allocation_fail=0; rates[0].mMinimum=rates[0].mMaximum=0;
|
||||
rates[1].mMinimum=48000; rates[1].mMaximum=32000;
|
||||
assert(avfoundation_nearest_aac_sample_rate(22050)==22050);
|
||||
mode=0;
|
||||
rates[0].mMinimum=rates[0].mMaximum=8000;
|
||||
rates[1].mMinimum=rates[1].mMaximum=22050;
|
||||
assert(avfoundation_nearest_aac_sample_rate(8000)==8000);
|
||||
assert(avfoundation_nearest_aac_sample_rate(22050)==22050);
|
||||
puts("PASS: AAC rate ranges, actual response size, malformed ranges and fallback failures");
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Run the recorder's submission control with portable Apple message stand-ins."""
|
||||
import argparse
|
||||
import os
|
||||
from pathlib import Path
|
||||
import re
|
||||
import shlex
|
||||
import subprocess
|
||||
import tempfile
|
||||
|
||||
here = Path(__file__).resolve().parent
|
||||
root = here.parents[2]
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--source', type=Path, default=root / 'record/drivers/record_avfoundation.m')
|
||||
args = parser.parse_args()
|
||||
source = args.source.read_text(encoding='utf-8')
|
||||
fragments = []
|
||||
for kind, buffer, extra in [('video', 'pixelBuffer', ', int presentationTime'),
|
||||
('audio', 'sampleBuffer', '')]:
|
||||
start = source.index('static bool avfoundation_record_push_' + kind + '(')
|
||||
end = source.index('\nstatic bool ', start + 1)
|
||||
function = source[start:end].rstrip()
|
||||
tail = function[function.index(' /* Append'):]
|
||||
declaration = re.search(r'__block bool success\s*=\s*false;', function)
|
||||
prefix = ('static bool legacy_' + kind + '_submit(record_avfoundation_t *handle, void *'
|
||||
+ buffer + extra + ')\n{\n'
|
||||
+ (declaration.group(0) + '\n' if declaration else '') + ' {\n')
|
||||
fragments.append(prefix + tail)
|
||||
start = source.find(' if (![handle->assetWriter canApplyOutputSettings:audioSettings')
|
||||
body = ''
|
||||
if start >= 0:
|
||||
body = source[start:source.index(' /* Create audio input */', start)]
|
||||
fragments.append('static void *legacy_audio_settings(record_avfoundation_t *handle, '
|
||||
'NSMutableDictionary *audioSettings)\n{\n' + body
|
||||
+ '\n return audioSettings;\n}\n')
|
||||
code = '\n'.join(fragments).replace('__block ', '')
|
||||
code = code.replace('dispatch_sync(handle->encodingQueue, ^{', '{').replace('});', '}')
|
||||
for kind in ('video', 'audio'):
|
||||
code = code.replace('handle->' + kind + 'Input.readyForMoreMediaData',
|
||||
'handle->' + kind + 'Input->ready')
|
||||
code = code.replace('handle->assetWriter.status', 'handle->assetWriter->status')
|
||||
code = re.sub(r'\[handle->pixelBufferAdaptor appendPixelBuffer:pixelBuffer\s+'
|
||||
r'withPresentationTime:presentationTime\]',
|
||||
'append_video(handle->pixelBufferAdaptor, pixelBuffer, presentationTime)', code)
|
||||
code = code.replace('[handle->audioInput appendSampleBuffer:sampleBuffer]',
|
||||
'append_audio(handle->audioInput, sampleBuffer)')
|
||||
code = re.sub(r'\[handle->assetWriter canApplyOutputSettings:audioSettings\s+'
|
||||
r'forMediaType:AVMediaTypeAudio\]', 'can_apply(handle->assetWriter, audioSettings)', code)
|
||||
code = code.replace('[NSMutableDictionary dictionaryWithDictionary:audioSettings]',
|
||||
'copy_settings(audioSettings)')
|
||||
code = code.replace('[fallback removeObjectForKey:AVEncoderBitRateKey]',
|
||||
'remove_bitrate(fallback, AVEncoderBitRateKey)')
|
||||
assert not any(token in code for token in ('dispatch_sync', '^{', '[handle', '[fallback', '[NSMutableDictionary'))
|
||||
compiler = shlex.split(os.environ.get('CC', 'cc'))
|
||||
flags = shlex.split(os.environ.get('CFLAGS', '-std=c89 -pedantic -Wall -Wextra -Werror'))
|
||||
with tempfile.TemporaryDirectory(prefix='avf-append-') as temp:
|
||||
temp = Path(temp)
|
||||
(temp / 'legacy_append.inc').write_text(code, encoding='utf-8')
|
||||
suffix = '.exe' if os.name == 'nt' or 'mingw' in compiler[0] else ''
|
||||
binary = temp / ('append' + suffix)
|
||||
command = compiler + flags + ['-I' + str(temp), '-I' + str(root / 'libretro-common/include'),
|
||||
str(here / 'legacy_append_test.c'), '-o', str(binary)]
|
||||
command += shlex.split(os.environ.get('LDFLAGS', ''))
|
||||
subprocess.run(command, cwd=here, check=True)
|
||||
subprocess.run(shlex.split(os.environ.get('RUNNER', '')) + [str(binary)],
|
||||
cwd=here, check=True, timeout=60)
|
||||
@@ -0,0 +1,124 @@
|
||||
/* Copyright (C) 2026 The RetroArch team. SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <boolean.h>
|
||||
|
||||
enum { AVAssetWriterStatusWriting = 1, AVEncoderBitRateKey = 1 };
|
||||
typedef struct
|
||||
{
|
||||
int status;
|
||||
bool preset_supported, default_supported;
|
||||
unsigned probes;
|
||||
} writer_t;
|
||||
typedef struct
|
||||
{
|
||||
bool ready, accepted;
|
||||
unsigned calls;
|
||||
} input_t;
|
||||
typedef struct
|
||||
{
|
||||
writer_t *assetWriter;
|
||||
input_t *videoInput, *audioInput, *pixelBufferAdaptor;
|
||||
} record_avfoundation_t;
|
||||
typedef struct { bool bitrate; } NSMutableDictionary;
|
||||
static NSMutableDictionary fallback_settings;
|
||||
static unsigned released, freed, removed;
|
||||
|
||||
static void release_sample(void *sample)
|
||||
{
|
||||
assert(sample);
|
||||
released++;
|
||||
}
|
||||
#define CVPixelBufferRelease release_sample
|
||||
#define CFRelease release_sample
|
||||
static bool append_video(input_t *input, void *sample, int timestamp)
|
||||
{
|
||||
assert(sample && timestamp == 7);
|
||||
input->calls++;
|
||||
return input->accepted;
|
||||
}
|
||||
static bool append_audio(input_t *input, void *sample)
|
||||
{
|
||||
assert(sample);
|
||||
input->calls++;
|
||||
return input->accepted;
|
||||
}
|
||||
static bool can_apply(writer_t *writer, NSMutableDictionary *settings)
|
||||
{
|
||||
writer->probes++;
|
||||
return settings->bitrate ? writer->preset_supported
|
||||
: writer->default_supported;
|
||||
}
|
||||
static NSMutableDictionary *copy_settings(NSMutableDictionary *settings)
|
||||
{
|
||||
fallback_settings = *settings;
|
||||
return &fallback_settings;
|
||||
}
|
||||
static void remove_bitrate(NSMutableDictionary *settings, int key)
|
||||
{
|
||||
assert(key == AVEncoderBitRateKey);
|
||||
settings->bitrate = false;
|
||||
removed++;
|
||||
}
|
||||
static void avfoundation_release_handle(record_avfoundation_t *handle)
|
||||
{
|
||||
assert(handle);
|
||||
freed++;
|
||||
}
|
||||
static void trace(const char *format, ...)
|
||||
{
|
||||
assert(format);
|
||||
}
|
||||
#define RARCH_ERR trace
|
||||
#define RARCH_WARN trace
|
||||
#include "legacy_append.inc"
|
||||
|
||||
int main(void)
|
||||
{
|
||||
writer_t writer = {0};
|
||||
input_t input = {0};
|
||||
record_avfoundation_t handle;
|
||||
NSMutableDictionary settings;
|
||||
unsigned status, ready, accepted, audio, cases = 0;
|
||||
handle.assetWriter = &writer;
|
||||
handle.videoInput = handle.audioInput = handle.pixelBufferAdaptor = &input;
|
||||
for (audio = 0; audio < 2; audio++)
|
||||
for (status = 0; status < 5; status++)
|
||||
for (ready = 0; ready < 2; ready++)
|
||||
for (accepted = 0; accepted < 2; accepted++)
|
||||
{
|
||||
bool expected = status == AVAssetWriterStatusWriting
|
||||
&& ready && accepted;
|
||||
bool result;
|
||||
writer.status = (int)status;
|
||||
input.ready = ready != 0;
|
||||
input.accepted = accepted != 0;
|
||||
input.calls = released = 0;
|
||||
result = audio ? legacy_audio_submit(&handle, &input)
|
||||
: legacy_video_submit(&handle, &input, 7);
|
||||
assert(result == expected && released == 1);
|
||||
assert(input.calls == (unsigned)(status
|
||||
== AVAssetWriterStatusWriting && ready));
|
||||
cases++;
|
||||
}
|
||||
for (ready = 0; ready < 2; ready++)
|
||||
for (accepted = 0; accepted < 2; accepted++)
|
||||
{
|
||||
void *result;
|
||||
settings.bitrate = true;
|
||||
writer.preset_supported = ready != 0;
|
||||
writer.default_supported = accepted != 0;
|
||||
writer.probes = freed = removed = 0;
|
||||
result = legacy_audio_settings(&handle, &settings);
|
||||
assert(!!result == !!(ready || accepted));
|
||||
assert(settings.bitrate);
|
||||
assert(writer.probes == (ready ? 1u : 2u));
|
||||
assert(removed == (ready ? 0u : 1u));
|
||||
assert(freed == (unsigned)(!ready && !accepted));
|
||||
if (result)
|
||||
assert(((NSMutableDictionary*)result)->bitrate == (ready != 0));
|
||||
}
|
||||
printf("PASS: %u production append/result cases and AAC preset/default/refusal paths; Apple messages are stand-ins\n", cases);
|
||||
return 0;
|
||||
}
|
||||
Reference in new issue
Block a user