How to record voice notes through bluetoothctl on Raspberry Pi

Ats
7 min readFeb 3, 2024

--

I played with Bluetooth last week. I decided to write this article to organize my learnings.

Photo by Brett Jordan on Unsplash

First of all

These are my development environments

Hardware

  • Raspberry Pi CM4 Model B
  • Raspberry Pi CM4 IO board
  • Beats Flex Wireless Bluetooth In-Ear Headphones with Mic

Software

  • Raspberry Pi OS Bookworm
  • BlueZ v5.66

Background

I don’t use Bluetooth for my job now. However, I could need the skill shortly and I had a week which I took for fun. So I decided to take the last week to get familiar with Bluetooth.

I usually use Python for Raspberry Pi and did a quick research for it. Then Bleak came to me first.

When I saw the number of stars in GitHub, I was like “It should be easy.” But it was not so straightforward. I got some errors when I tried to connect with my headset and I couldn’t understand what was going on from the error logs even after I googled the error message. Bleak is a tool to control bluetoothctl with Python. So I started to figure out whether I could do without Python using bluetoothctl directly. What I wanted to do was to connect with my headset, play sound, and record my voice notes.

What I did

This is the docs about bluetoothctl

I took a look at the docs but it’s not so useful. Basically, I repeated to try something and google the error.

Connect

I was following the article.

However, when I ran the pair command, I had trouble, which was that I needed to confirm the pairing with 6 digits. Obviously, my headset doesn’t have any keyboards or displays. So I needed to turn off the confirmation. I googled it and found the following forum.

So bluetoothctl has a default agent to confirm pairing, which is DisplayYesNo . So I need to turn off the agent or set NoInputNoOutput as agent.

The following commands are what I did in bluetoothctl or bluetoothctl --agent NoInputNoOutput . One note. agent no works the same as agent off but I didn’t find any resources for agent no

[bluetooth]# agent off
Agent unregistered
[bluetooth]# scan on
Discovery started
[CHG] Controller DC:A6:32:FF:B1:CE Discovering: yes
[NEW] Device 1D:53:12:55:EB:E6 1D-53-12-55-EB-E6
...
[bluetooth]# devices
Device A8:91:3D:DB:EE:A0 Beats Flex
Device 74:45:CE:75:6C:32 ATH-SQ1TW
Device 1D:53:12:55:EB:E6 1D-53-12-55-EB-E6
...
[bluetooth]# pair A8:91:3D:DB:EE:A0
Attempting to pair with A8:91:3D:DB:EE:A0
[CHG] Device A8:91:3D:DB:EE:A0 Connected: yes
[CHG] Device A8:91:3D:DB:EE:A0 Bonded: yes
[CHG] Device A8:91:3D:DB:EE:A0 UUIDs: 00001000-0000-1000-8000-00805f9b34fb
[CHG] Device A8:91:3D:DB:EE:A0 UUIDs: 00001101-0000-1000-8000-00805f9b34fb
[CHG] Device A8:91:3D:DB:EE:A0 UUIDs: 0000110b-0000-1000-8000-00805f9b34fb
[CHG] Device A8:91:3D:DB:EE:A0 UUIDs: 0000110c-0000-1000-8000-00805f9b34fb
[CHG] Device A8:91:3D:DB:EE:A0 UUIDs: 0000110e-0000-1000-8000-00805f9b34fb
[CHG] Device A8:91:3D:DB:EE:A0 UUIDs: 0000111e-0000-1000-8000-00805f9b34fb
[CHG] Device A8:91:3D:DB:EE:A0 UUIDs: 00001200-0000-1000-8000-00805f9b34fb
[CHG] Device A8:91:3D:DB:EE:A0 UUIDs: 02030302-1d19-415f-86f2-22a2106a0a78
[CHG] Device A8:91:3D:DB:EE:A0 UUIDs: 74ec2172-0bad-4d01-8f77-997b2be0722a
[CHG] Device A8:91:3D:DB:EE:A0 ServicesResolved: yes
[CHG] Device A8:91:3D:DB:EE:A0 Paired: yes
Pairing successful
[bluetooth]# connect A8:91:3D:DB:EE:A0
Attempting to connect to A8:91:3D:DB:EE:A0
...
[Beats Flex]# info
Device A8:91:3D:DB:EE:A0 (public)
Name: Beats Flex
Alias: Beats Flex
Class: 0x00240418
Icon: audio-headphones
Paired: yes
Bonded: yes
Trusted: no
Blocked: no
Connected: yes
LegacyPairing: no
UUID: Service Discovery Serve.. (00001000-0000-1000-8000-00805f9b34fb)
UUID: Serial Port (00001101-0000-1000-8000-00805f9b34fb)
UUID: Audio Sink (0000110b-0000-1000-8000-00805f9b34fb)
UUID: A/V Remote Control Target (0000110c-0000-1000-8000-00805f9b34fb)
UUID: Advanced Audio Distribu.. (0000110d-0000-1000-8000-00805f9b34fb)
UUID: A/V Remote Control (0000110e-0000-1000-8000-00805f9b34fb)
UUID: Handsfree (0000111e-0000-1000-8000-00805f9b34fb)
UUID: PnP Information (00001200-0000-1000-8000-00805f9b34fb)
UUID: Vendor specific (02030302-1d19-415f-86f2-22a2106a0a78)
UUID: Vendor specific (74ec2172-0bad-4d01-8f77-997b2be0722a)
Modalias: bluetooth:v004Cp2010d0372

Play sound

Still following the article. I would say nothing difficult here.

The following commands are what I did in the terminal.

paplay your_sound_file.wav

Record voice notes

I followed the article to record. Also, I’ve attached the docs of options for arecord

One thing I want to mention here is Device Profiles, which are the settings for the input and output of the Bluetooth device. You can find more details in the following article.

What happened to me was that I could hear sounds from my headset but I couldn’t record my voice with it. After reading the article, I checked my device profile following the web page.

$ pactl list cards
Card #70
Name: bluez_card.A8_91_3D_DB_EE_A0
Driver: module-bluez5-device.c
Owner Module: n/a
Properties:
api.bluez5.address = "A8:91:3D:DB:EE:A0"
api.bluez5.class = "0x240418"
api.bluez5.connection = "disconnected"
api.bluez5.device = ""
api.bluez5.icon = "audio-headphones"
api.bluez5.path = "/org/bluez/hci0/dev_A8_91_3D_DB_EE_A0"
bluez5.auto-connect = "[ hfp_hf hsp_hs a2dp_sink ]"
bluez5.profile = "off"
device.alias = "Beats Flex"
device.api = "bluez5"
device.bus = "bluetooth"
device.description = "Beats Flex"
device.form_factor = "headphone"
device.icon_name = "audio-headphones-bluetooth"
device.name = "bluez_card.A8_91_3D_DB_EE_A0"
device.product.id = "0x2010"
device.string = "A8:91:3D:DB:EE:A0"
device.vendor.id = "bluetooth:004c"
media.class = "Audio/Device"
factory.id = "14"
client.id = "34"
object.id = "70"
object.serial = "70"
Profiles:
off: Off (sinks: 0, sources: 0, priority: 0, available: yes)
a2dp-sink: High Fidelity Playback (A2DP Sink) (sinks: 1, sources: 0, priority: 16, available: yes)
headset-head-unit: Headset Head Unit (HSP/HFP) (sinks: 1, sources: 1, priority: 1, available: yes)
a2dp-sink-sbc: High Fidelity Playback (A2DP Sink, codec SBC) (sinks: 1, sources: 0, priority: 18, available: yes)
a2dp-sink-sbc_xq: High Fidelity Playback (A2DP Sink, codec SBC-XQ) (sinks: 1, sources: 0, priority: 17, available: yes)
headset-head-unit-cvsd: Headset Head Unit (HSP/HFP, codec CVSD) (sinks: 1, sources: 1, priority: 2, available: yes)
headset-head-unit-msbc: Headset Head Unit (HSP/HFP, codec mSBC) (sinks: 1, sources: 1, priority: 3, available: yes)
Active Profile: a2dp-sink-sbc
Ports:
headphone-output: Headphone (type: Headphones, priority: 0, latency offset: 0 usec, available)
Properties:
port.type = "headphones"
Part of profile(s): a2dp-sink, a2dp-sink-sbc, a2dp-sink-sbc_xq
headphone-hf-input: Handsfree (type: Headphones, priority: 0, latency offset: 0 usec, available)
Properties:
port.type = "headphones"
Part of profile(s): headset-head-unit, headset-head-unit-cvsd, headset-head-unit-msbc
headphone-hf-output: Handsfree (type: Headphones, priority: 0, latency offset: 0 usec, available)
Properties:
port.type = "headphones"
Part of profile(s): headset-head-unit, headset-head-unit-cvsd, headset-head-unit-msbc

Then I realized the device profile is a2dp-sink-sbc . So I needed to change from A2DP to HSP. So I changed it like below.

$ pactl set-card-profile [Your Card ID] headset-head-unit-msbc
$ pactl set-card-profile 70 headset-head-unit-msbc # in my case

Finally, I recorded my voice notes following arecord command.

arecord -d 10 sample.wav

I got along with bluetoothctl so I’ll return back to Bleak and play with it again.

That’s it!

Appendix

  • bluetoothctlcommand lists
  • bluetoothctl connect/pair errors

org.bluez.Error.NotReady

Failed to pair: org.bluez.Error.AlreadyExists

  • Check Bluetooth status
$ sudo service bluetooth status
● bluetooth.service - Bluetooth service
Loaded: loaded (/lib/systemd/system/bluetooth.service; enabled; preset: enabled)
Active: active (running) since Sat 2024-02-03 19:10:47 GMT; 5min ago
Docs: man:bluetoothd(8)
Main PID: 769 (bluetoothd)
Status: "Running"
Tasks: 1 (limit: 3912)
CPU: 216ms
CGroup: /system.slice/bluetooth.service
└─769 /usr/libexec/bluetooth/bluetoothd

Feb 03 19:10:57 blue bluetoothd[769]: Endpoint registered: sender=:1.40 path=/MediaEndpoint/A2DPSource/aptx_ll_1
Feb 03 19:10:57 blue bluetoothd[769]: Endpoint registered: sender=:1.40 path=/MediaEndpoint/A2DPSource/aptx_ll_0
Feb 03 19:10:57 blue bluetoothd[769]: Endpoint registered: sender=:1.40 path=/MediaEndpoint/A2DPSource/aptx_ll_duplex_1
Feb 03 19:10:57 blue bluetoothd[769]: Endpoint registered: sender=:1.40 path=/MediaEndpoint/A2DPSource/aptx_ll_duplex_0
Feb 03 19:10:57 blue bluetoothd[769]: Endpoint registered: sender=:1.40 path=/MediaEndpoint/A2DPSource/faststream
Feb 03 19:10:57 blue bluetoothd[769]: Endpoint registered: sender=:1.40 path=/MediaEndpoint/A2DPSource/faststream_duplex
Feb 03 19:10:57 blue bluetoothd[769]: Endpoint registered: sender=:1.40 path=/MediaEndpoint/A2DPSink/opus_05
Feb 03 19:10:57 blue bluetoothd[769]: Endpoint registered: sender=:1.40 path=/MediaEndpoint/A2DPSource/opus_05
Feb 03 19:10:57 blue bluetoothd[769]: Endpoint registered: sender=:1.40 path=/MediaEndpoint/A2DPSink/opus_05_duplex
Feb 03 19:10:57 blue bluetoothd[769]: Endpoint registered: sender=:1.40 path=/MediaEndpoint/A2DPSource/opus_05_duplex

If there is an error in the log, restart Bluetooth with sudo service bluetooth restart

--

--

Ats
Ats

Written by Ats

I like building something tangible like touch, gesture, and voice. Ruby on Rails / React Native / Yocto / Raspberry Pi / Interaction Design / CIID IDP alumni

No responses yet