Skip to content

Commit

Permalink
Merge branch 'main' of github.com:nonstrict-hq/nonstrict.eu-website
Browse files Browse the repository at this point in the history
  • Loading branch information
tomlokhorst committed Dec 3, 2024
2 parents e0a5ddb + aab5388 commit 48e946d
Show file tree
Hide file tree
Showing 5 changed files with 123 additions and 6 deletions.
4 changes: 2 additions & 2 deletions bezel/Sources/BezelWebsite/Theme/HeroAlt.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@
class="mx-auto max-w-xl min-h-[24rem] sm:min-h-[448px] lg:min-h-min lg:max-h-[448px] flex-shrink-0 flex flex-col justify-center gap-8 lg:mx-0 lg:mt-8">
<div>
<p class="lg:-mt-8 mb-4">
<a href="/bezel/#record"
<a href="/bezel/pricing"
class="rounded-full bg-green-600 py-1.5 px-4 text-sm font-medium tracking-wide text-white hover:underline">
<svg class="max-[389px]:hidden h-4 w-4 fill-yellow-300 inline mr-2" viewBox="0 0 24 24">
<path fill-rule="evenodd"
d="M9 4.5a.75.75 0 01.721.544l.813 2.846a3.75 3.75 0 002.576 2.576l2.846.813a.75.75 0 010 1.442l-2.846.813a3.75 3.75 0 00-2.576 2.576l-.813 2.846a.75.75 0 01-1.442 0l-.813-2.846a3.75 3.75 0 00-2.576-2.576l-2.846-.813a.75.75 0 010-1.442l2.846-.813A3.75 3.75 0 007.466 7.89l.813-2.846A.75.75 0 019 4.5zM18 1.5a.75.75 0 01.728.568l.258 1.036c.236.94.97 1.674 1.91 1.91l1.036.258a.75.75 0 010 1.456l-1.036.258c-.94.236-1.674.97-1.91 1.91l-.258 1.036a.75.75 0 01-1.456 0l-.258-1.036a2.625 2.625 0 00-1.91-1.91l-1.036-.258a.75.75 0 010-1.456l1.036-.258a2.625 2.625 0 001.91-1.91l.258-1.036A.75.75 0 0118 1.5zM16.5 15a.75.75 0 01.712.513l.394 1.183c.15.447.5.799.948.948l1.183.395a.75.75 0 010 1.422l-1.183.395c-.447.15-.799.5-.948.948l-.395 1.183a.75.75 0 01-1.422 0l-.395-1.183a1.5 1.5 0 00-.948-.948l-1.183-.395a.75.75 0 010-1.422l1.183-.395c.447-.15.799-.5.948-.948l.395-1.183A.75.75 0 0116.5 15z"
clip-rule="evenodd"></path>
</svg>Bezel v2: Introducing Recording <span aria-hidden="true">&rarr;</span></a>
</svg>Black Friday Deal — Save 50% <span aria-hidden="true">&rarr;</span></a>
</p>
<h1 class="text-4xl font-bold tracking-tight text-gray-900 dark:text-white sm:text-6xl">Mirror any
iPhone<br />on your Mac</h1>
Expand Down
11 changes: 7 additions & 4 deletions bezel/Sources/BezelWebsite/Theme/Pricing.html
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@ <h2 class="text-4xl font-bold tracking-tight text-gray-900 dark:text-white sm:te

<div class="relative mx-auto mt-2 mb-1 max-w-sm rounded-3xl p-8 ring-1 xl:p-10 ring-gray-300 dark:ring-gray-200 bg-white">
<h3 id="bezel-license" class="text-lg font-semibold leading-8 text-gray-900">Bezel Pro &mdash; 1 Mac</h3>
<p class="absolute top-0 -translate-y-1/2 transform rounded-full bg-green-600 -ml-4 py-1.5 px-4 text-sm font-semibold uppercase tracking-wide text-white">Black Friday Deal — 50% Off</p>
<p class="mt-1 flex items-baseline gap-x-1">
<span class="text-4xl font-bold tracking-tight text-gray-900">$29</span>
<span class="relative text-3xl font-bold tracking-tight text-gray-500"><span class="absolute top-1/2 left-0 h-[3px] w-full -translate-y-1/2 -rotate-[12deg] bg-current "></span>$29</span>
<span class="ml-1 text-4xl font-bold tracking-tight text-gray-900">$14.50</span>
</p>
<p class="mt-1 text-sm leading-6 text-gray-600">Pay-once license for your Mac.</p>
<ul role="list" class="mt-8 space-y-3 text-sm leading-6 xl:mt-10 text-gray-600">
Expand Down Expand Up @@ -49,9 +51,10 @@ <h3 id="bezel-license" class="text-lg font-semibold leading-8 text-gray-900">Bez
</div>
<div class="relative mx-auto mt-2 mb-1 max-w-sm rounded-3xl p-8 ring-1 xl:p-10 ring-gray-300 dark:ring-gray-200 bg-white">
<h3 id="bezel-license" class="text-lg font-semibold leading-8 text-gray-900">Bezel Pro &mdash; 3 Macs</h3>
<p class="absolute top-0 -translate-y-1/2 transform rounded-full bg-green-600 -ml-4 py-1.5 px-4 text-sm font-semibold uppercase tracking-wide text-white"><svg class="h-4 w-4 fill-yellow-300 inline mr-2" viewBox="0 0 24 24"><path fill-rule="evenodd" d="M9 4.5a.75.75 0 01.721.544l.813 2.846a3.75 3.75 0 002.576 2.576l2.846.813a.75.75 0 010 1.442l-2.846.813a3.75 3.75 0 00-2.576 2.576l-.813 2.846a.75.75 0 01-1.442 0l-.813-2.846a3.75 3.75 0 00-2.576-2.576l-2.846-.813a.75.75 0 010-1.442l2.846-.813A3.75 3.75 0 007.466 7.89l.813-2.846A.75.75 0 019 4.5zM18 1.5a.75.75 0 01.728.568l.258 1.036c.236.94.97 1.674 1.91 1.91l1.036.258a.75.75 0 010 1.456l-1.036.258c-.94.236-1.674.97-1.91 1.91l-.258 1.036a.75.75 0 01-1.456 0l-.258-1.036a2.625 2.625 0 00-1.91-1.91l-1.036-.258a.75.75 0 010-1.456l1.036-.258a2.625 2.625 0 001.91-1.91l.258-1.036A.75.75 0 0118 1.5zM16.5 15a.75.75 0 01.712.513l.394 1.183c.15.447.5.799.948.948l1.183.395a.75.75 0 010 1.422l-1.183.395c-.447.15-.799.5-.948.948l-.395 1.183a.75.75 0 01-1.422 0l-.395-1.183a1.5 1.5 0 00-.948-.948l-1.183-.395a.75.75 0 010-1.422l1.183-.395c.447-.15.799-.5.948-.948l.395-1.183A.75.75 0 0116.5 15z" clip-rule="evenodd"></path></svg>Best value</p>
<p class="absolute top-0 -translate-y-1/2 transform rounded-full bg-green-600 -ml-4 py-1.5 px-4 text-sm font-semibold uppercase tracking-wide text-white">Black Friday Deal — 50% Off</p>
<p class="mt-1 flex items-baseline gap-x-1">
<span class="text-4xl font-bold tracking-tight text-gray-900">$69</span>
<span class="relative text-3xl font-bold tracking-tight text-gray-500"><span class="absolute top-1/2 left-0 h-[3px] w-full -translate-y-1/2 -rotate-[12deg] bg-current "></span>$69</span>
<span class="ml-1 text-4xl font-bold tracking-tight text-gray-900">$34.50</span>
</p>
<p class="mt-1 text-sm leading-6 text-gray-600">Great for multi-device setups.</p>
<ul role="list" class="mt-8 space-y-3 text-sm leading-6 xl:mt-10 text-gray-600">
Expand Down Expand Up @@ -217,7 +220,7 @@ <h3 class="mt-8 mb-2 text-lg text-gray-900 dark:text-white font-semibold">How do

function openCheckout(licenseType) {
const priceId = priceIdLookup[licenseType];
Paddle.Checkout.open({items:[{quantity: licenseType == 'team' ? 5 : 1,priceId}]});
Paddle.Checkout.open({discountCode:'BLACKFRIDAY24',items:[{quantity: licenseType == 'team' ? 5 : 1,priceId}]});
window.plausible('Bezel Checkout Opened')
}

Expand Down
16 changes: 16 additions & 0 deletions recordkit/changelog.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,21 @@
# Changelog

### 0.20.0

- Added `RKCameraPreview` to preview a camera in SwiftUI.
- Apple device recordings now also capture audio from the device.
- Recorder is more robust when prepare/start/stop are called multiple times.
- The start method on a recorder can now throw an error if something fails.
- Improved precision of recorder start/stop.
- Improved window filtering when listing available windows.
- Improved resillience of all audio recorders when audio gaps occur.
- Segmented output now has correct video dimensions for Apple device recordings.
- Improved error messages through the SDK.
- Improved log messages through the SDK.
- Swift: Options to exclude windows when recording a display.
- Swift: Improved device discovery API.
- Swift: Ability to store/retrieve prefered microphone, camera and display.

### 0.19.0

- Electron: Add option to receive RecordKit logs through `recordkit.on('log', (logEvent) => { })`.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
---
date: 2024-11-23 12:00
authors: mathijs, tom
tags: Engineering, RecordKit
title: Handling audio capture gaps on macOS
description: Audio capture on macOS can sometimes contain gaps. This article explains how to detect and handle these gaps to maintain proper audio/video sync when recording to files.
image: images/blog/victoria-shes-IUk1S6n2s0o-unsplash.jpg
path: 2024/handling-audio-capture-gaps-on-macos
featured: true
---

**tldr; Missing audio samples during recording can cause audio/video desynchronization. Detect gaps using presentation timestamps and fill them with silent audio samples to maintain proper sync.**

Audio capture presents unique challenges due to its real-time, continuous nature. While macOS provides robust APIs for capturing and recording audio to files, certain scenarios can lead to gaps in the audio stream, causing synchronization issues with simulaniously recorded video.

## Understanding the Problem

When capturing audio on macOS, the system occasionally fails to deliver a continuous stream of samples to applications. This issue seems to manifest, among other scenarios, during audio device switches, such as connecting AirPods mid-recording. While Core Audio logs errors to the console, the recording continues - but with potentially missing samples.

The issue has been observed with both `AVCaptureSession` and `AVAudioEngine` based capture implementations, suggesting the problem exists at a lower level in the Core Audio stack rather than being specific to a particular capture API.

For real-time playback, these gaps result in momentary audio glitches. However, when recording to a file, the consequences are more severe. The missing samples cause the audio track to become shorter than other recorded media, leading to desynchronization with video tracks.

<div class="not-prose flex space-x-4 border-2 border-orange-500 rounded-lg pl-4 pr-6 py-6 mt-8 -mb-6">
<div class="flex-initial">
<a href="/bezel?utm_source=nonstrict&utm_medium=blog&utm_content=hkworkoutsession-remote-delegate-not-setup-error" target="_blank"><img src="/images/bezel-icon.png" class="max-h-full max-w-10 m-0"></a>
</div>
<div class="flex-initial">
<h3 class="text-2xl font-bold text-black hover:text-orange-500 leading-relaxed mt-0 mb-2"><a href="/bezel?utm_source=nonstrict&utm_medium=blog&utm_content=hkworkoutsession-remote-delegate-not-setup-error" target="_blank">Bezel · Mirror your iPhone on your Mac</a></h3>
<p class="mb-2">Perfect for app demos & presentations; Simply plug in an iPhone and it automatically shows up on your Mac.</p>
<p><a href="/bezel?utm_source=nonstrict&utm_medium=blog&utm_content=hkworkoutsession-remote-delegate-not-setup-error" target="_blank" class="text-orange hover:text-orange-500 underline font-medium">Learn more →</a></p>
</div>
<div class="flex-initial hidden md:block">
<a href="/bezel?utm_source=nonstrict&utm_medium=blog&utm_content=hkworkoutsession-remote-delegate-not-setup-error" target="_blank">
<img src="/images/bezel-still.jpg" class="max-h-full max-w-36 rounded-md bg-white/5 ring-1 ring-gray-600/50 dark:ring-white/50 lg:mt-auto">
</a>
</div>
</div>

## Detection and Solution

The key to addressing this issue lies in monitoring the presentation timestamps of incoming `CMSampleBuffer` objects. Audio devices generate samples at a fixed rate, which serves as a clock for generating presentation times and calculating sample durations. The sample timings should form a continuous sequence.

This can be used to implement a method that fills the gaps in the audio sequence:

```swift
var nextExpectedAudioTime: CMTime?
func processSampleBuffer(_ sampleBuffer: CMSampleBuffer) {
// TODO: Probably wise to check if the sample buffer times are all valid

// Ensure to always set the next expected time
defer { nextExpectedAudioTime = sampleBuffer.presentationTimeStamp + sampleBuffer.duration }

guard let nextExpectedAudioTime else {
// Handle first sample
writeToFile(sampleBuffer)
return
}

let delta = sampleBuffer.presentationTimeStamp - nextExpectedAudioTime
if delta > .zero {
let silentAudio = generateSilentAudio(duration: delta)
writeToFile(sampleBuffer)
}

writeToFile(sampleBuffer)
}
```

When a gap is detected, generating and inserting silent audio samples maintains the proper timing relationship between audio and other recorded media. While this approach still results in a brief audio dropout, it prevents the more problematic issue of audio/video desynchronization throughout the remainder of the recording.

## Note on Reliability

This issue doesn't affect all users equally. It's been observed across different harware setups and macOS versions, with most reports coming from macOS 14. It is particularly noticeable for us during testing when switching the audio output device.

This seems to be an issue inside Core Audio that we hope will be resolved over time. However, this workaround of inserting silent audio at least maintains proper audio/video sync. It has been successfully used in [RecordKit](https://recordkit.dev/), our SDK for macOS recording applications for some time in production now.

<div class="not-prose flex space-x-4 border-2 border-orange-500 rounded-lg pl-4 pr-6 py-6 mt-8 -mb-6">
<div class="flex-initial">
<a href="/bezel?utm_source=nonstrict&utm_medium=blog&utm_content=hkworkoutsession-remote-delegate-not-setup-error" target="_blank"><img src="/images/bezel-icon.png" class="max-h-full max-w-10 m-0"></a>
</div>
<div class="flex-initial">
<h3 class="text-2xl font-bold text-black hover:text-orange-500 leading-relaxed mt-0 mb-2"><a href="/bezel?utm_source=nonstrict&utm_medium=blog&utm_content=hkworkoutsession-remote-delegate-not-setup-error" target="_blank">Bezel · Mirror your iPhone on your Mac</a></h3>
<p class="mb-2">Perfect for app demos & presentations; Simply plug in an iPhone and it automatically shows up on your Mac.</p>
<p><a href="/bezel?utm_source=nonstrict&utm_medium=blog&utm_content=hkworkoutsession-remote-delegate-not-setup-error" target="_blank" class="text-orange hover:text-orange-500 underline font-medium">Learn more →</a></p>
</div>
<div class="flex-initial hidden md:block">
<a href="/bezel?utm_source=nonstrict&utm_medium=blog&utm_content=hkworkoutsession-remote-delegate-not-setup-error" target="_blank">
<img src="/images/bezel-still.jpg" class="max-h-full max-w-36 rounded-md bg-white/5 ring-1 ring-gray-600/50 dark:ring-white/50 lg:mt-auto">
</a>
</div>
</div>

## References

- Apple. (2024). [Setting Up a Capture Session](https://developer.apple.com/documentation/avfoundation/setting-up-a-capture-session). Apple Developer Documentation.
- Apple. (2024). [AVCaptureSession](https://developer.apple.com/documentation/avfoundation/avcapturesession). Apple Developer Documentation.
- Apple. (2024). [AVAudioEngine](https://developer.apple.com/documentation/avfaudio/avaudioengine). Apple Developer Documentation.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 48e946d

Please sign in to comment.