audio hiccup when hovering mouse above youtube video thumbnail in Chrome on MacOS
Author: Wang-YueCreated Mar 3, 2026Updated Jul 21, 2026
Labelsbug
Please Confirm
- I have read the FAQ and Wiki where most common issues can be resolved
- I have searched Discussions to see if the same question has already been asked
- This is a bug and not a question about audio routing or configuration, which should be posted in Discussions
macOS Version
macOS 26 Tahoe
BlackHole Build(s) Affected
- 2 channel
- 16 channel
- 64 channel
- other/custom build
Describe the bug
similar to https://github.com/ExistentialAudio/BlackHole/issues/273
First reported to camilladsp https://github.com/HEnquist/camilladsp/issues/436 but quickly realized it's not a camilladsp issue.
Reproduction Steps
Step to repro
100% reproducible.
- install blackhole, Chrome, and camilladsp. The version I use is 3.0 and 4.0 (via next31 branch).
- run with the following config
---
devices:
samplerate: 44100
chunksize: 1024
enable_rate_adjust: true
capture:
type: CoreAudio
device: "BlackHole 2ch"
channels: 2
format: F32
playback:
type: CoreAudio
channels: 2
device: "MacBook Pro Speakers"
format: F32
exclusive: true
- Play any music via blackhole
- Open Chrome browser, navigate to youtube.com, hover mouse over any video clip thumbnail
Observed behavior
You will hear hiccup.
Log
I found nothing wrong in the log when running into this issue:
2026-03-03 01:16:57.965739 DEBUG [camillalib::coreaudiodevice] <src/coreaudiodevice.rs:1021> Rate watcher, measured sample rate is 44104.6 Hz.
2026-03-03 01:16:58.987274 DEBUG [camillalib::coreaudiodevice] <src/coreaudiodevice.rs:998> Measured sample rate is 44100.1 Hz.
2026-03-03 01:16:58.987342 DEBUG [camillalib::coreaudiodevice] <src/coreaudiodevice.rs:1021> Rate watcher, measured sample rate is 44103.3 Hz.
2026-03-03 01:17:00.009002 DEBUG [camillalib::coreaudiodevice] <src/coreaudiodevice.rs:998> Measured sample rate is 44098.1 Hz.Additional comment
- This bug only occurs with Chrome. Safari seems fine.
- Chrome works fine when audio is not routed through camilladsp and blackhole. => hovering mouse over video thumbnail will not cause audio hiccups if I don't run camilladsp and blackhole
- Other websites (such as instagram / twitter) have same issue with mouse hovering over video clip thumbnail.
Expected Behavior
audio should not exibit hiccups when playing
Screenshots
Camilladsp can be replaced by the following raw CoreAudio swift program
import Foundation
import CoreAudio
// --- Configuration ---
let inputDeviceName = "BlackHole 2ch"
let outputDeviceName = "MacBook Pro Speakers"
let bufferSize: UInt32 = 1024 * 128 // 128KB buffer
class AudioCircle {
var data = UnsafeMutableRawPointer.allocate(byteCount: Int(bufferSize), alignment: MemoryLayout<Float>.alignment)
var writeOffset: Int = 0
var readOffset: Int = 0
}
let sharedBuffer = AudioCircle()
// --- Helper: Find Device ID ---
func findDeviceID(named name: String) -> AudioDeviceID? {
var propertyAddress = AudioObjectPropertyAddress(
mSelector: kAudioHardwarePropertyDevices,
mScope: kAudioObjectPropertyScopeGlobal,
mElement: kAudioObjectPropertyElementMain
)
var dataSize: UInt32 = 0
AudioObjectGetPropertyDataSize(AudioObjectID(kAudioObjectSystemObject), &propertyAddress, 0, nil, &dataSize)
let deviceCount = Int(dataSize) / MemoryLayout<AudioDeviceID>.size
var deviceIDs = [AudioDeviceID](repeating: 0, count: deviceCount)
AudioObjectGetPropertyData(AudioObjectID(kAudioObjectSystemObject), &propertyAddress, 0, nil, &dataSize, &deviceIDs)
for id in deviceIDs {
var nameSize = UInt32(MemoryLayout<CFString?>.size)
var deviceName: CFString?
var nameAddress = AudioObjectPropertyAddress(
mSelector: kAudioDevicePropertyDeviceNameCFString,
mScope: kAudioObjectPropertyScopeGlobal,
mElement: kAudioObjectPropertyElementMain
)
AudioObjectGetPropertyData(id, &nameAddress, 0, nil, &nameSize, &deviceName)
if let nameStr = deviceName as String?, nameStr == name { return id }
}
return nil
}
// 1. Get Device IDs
guard let inputID = findDeviceID(named: inputDeviceName),
let outputID = findDeviceID(named: outputDeviceName) else {
print("Devices not found."); exit(1)
}
// 2. Input Callback (Device A -> Buffer)
let inputProc: AudioDeviceIOProc = { (inDevice, inNow, inInputData, inInputTime, outOutputData, inOutputTime, inClientData) -> OSStatus in
let ring = Unmanaged<AudioCircle>.fromOpaque(inClientData!).takeUnretainedValue()
// Access the first buffer in the list
let inputBuf = inInputData.pointee.mBuffers
if let src = inputBuf.mData {
let size = Int(inputBuf.mDataByteSize)
// Simple wrap-around logic
if ring.writeOffset + size <= Int(bufferSize) {
memcpy(ring.data + ring.writeOffset, src, size)
}
ring.writeOffset = (ring.writeOffset + size) % Int(bufferSize)
}
return noErr
}
// 3. Output Callback (Buffer -> Device B)
let outputProc: AudioDeviceIOProc = { (inDevice, inNow, inInputData, inInputTime, outOutputData, inOutputTime, inClientData) -> OSStatus in
let ring = Unmanaged<AudioCircle>.fromOpaque(inClientData!).takeUnretainedValue()
// Corrected access: Get the pointer to the AudioBuffer within the AudioBufferList
let outBufferList = outOutputData.pointee
let size = Int(outBufferList.mBuffers.mDataByteSize)
if let dest = outBufferList.mBuffers.mData {
if ring.readOffset + size <= Int(bufferSize) {
memcpy(dest, ring.data + ring.readOffset, size)
}
ring.readOffset = (ring.readOffset + size) % Int(bufferSize)
}
return noErr
}
// 4. Register and Start
var inputProcID: AudioDeviceIOProcID?
var outputProcID: AudioDeviceIOProcID?
let context = Unmanaged.passUnretained(sharedBuffer).toOpaque()
// Create IO Procs
AudioDeviceCreateIOProcID(inputID, inputProc, context, &inputProcID)
AudioDeviceCreateIOProcID(outputID, outputProc, context, &outputProcID)
// Start Hardware
AudioDeviceStart(inputID, inputProcID)
AudioDeviceStart(outputID, outputProcID)
print(" CoreAudio Router Active: \(inputDeviceName) -> \(outputDeviceName)")
print("Check Audio MIDI Setup to ensure both are at the same Sample Rate.")
CFRunLoopRun()
Source: ExistentialAudio/BlackHole