Let Your AI Agent Be Your Audio Engineer

Recording calls, podcasts, and voice-AI tests on your Mac — wired by a
prompt, no audio engineering degree required.

Everyone who records anything on a Mac hits the same wall eventually:

  • You interview a guest over Zoom and want your voice and theirs on
    separate tracks
    for editing — but your recording only has one side.
  • You’re on AirPods and want to capture a call exactly as you heard it.
  • You’re a developer testing a voice AI agent and need to feed it audio
    and record its replies, reproducibly.

The Mac can do all of this natively — no paid apps — but the setup lives in
a utility called Audio MIDI Setup: aggregate devices, multi-output devices,
clock sources, drift correction, sample rates. Checkboxes upon checkboxes.
It drives people crazy, and next month you get to re-remember all of it.

Here’s the new way: describe what you want to an AI coding agent, and let
it do the wiring.

The zero-learning path

If you have Claude Code, Codex, or any
coding agent that can run commands, you don’t need to understand anything
below. The workflow is four steps, in this order — it matters:

1. Install the tools. audictl is one command; BlackHole is a normal Mac
installer (your agent can fetch it, but the installer asks for your
password — that click is yours):

npm install -g @agora-build/audictl @agora-build/dialf
brew install blackhole-2ch blackhole-16ch     # or download from existential.audio

2. Plug everything in. Pair the AirPods, connect the headset, plug in
the interface — before asking for anything. An agent can only wire
devices it can see; hardware that isn’t connected doesn’t exist to
CoreAudio.

3. Have the agent take inventory. Paste:

Run audictl list and tell me what audio devices I have.

Now the agent knows your world by real names — “Hai’s AirPods Pro”, not a
guess — and everything it builds will reference devices that actually exist.

4. Say what you want, in plain English. From here the agent handles all
of it — wiring, verifying, recording, and cleaning up afterwards:

Read https://guoh.ai/lifelog/2026/08/let-your-ai-agent-be-your-audio-engineer/
and set up my Mac so my next call is recorded with my mic and the other
side on separate tracks. I’m on AirPods. Undo everything when I say done.

Read that same page and set up Case 2 for testing my voice agent at
48 kHz, then run the DialF eval.

Everything the agent runs is safe to repeat (commands are no-ops when
already done), nothing touches your files, and every step is a one-liner to
undo.

The rest of this post is what the agent (or you, if you’re curious)
actually does. Humans welcome; it’s shorter than the checkbox maze.

The kit

Three free pieces:

  • audictl — Audio MIDI Setup
    as a command line: devices, defaults, sample rates, aggregate and
    multi-output devices with clock and drift control. JSON output, meaningful
    exit codes, idempotent — built so AI agents can drive it safely.
  • BlackHole — a virtual audio
    cable. Anything played into it can be recorded from it. The 2ch and 16ch
    variants are two independent cables, so input and output never collide.
  • DialF
    (@agora-build/dialf) — plays prompt audio, captures replies, records
    tx/rx/mix on one clock. Built for voice-agent evals.

Case 1 — Record both sides of a call or interview

For podcasters, journalists, anyone taking calls on a headset. One
recording, in sync: channel 1 is your voice, channels 2–3 are what you heard
(your guest, the other side, the meeting). Split them afterwards and edit
each side separately.

Prompt to paste: “Set up my Mac to record my next call: my AirPods mic
and everything I hear, as separate channels in one file.”

What that builds:

flowchart LR
    app["app audio<br>(Zoom, Meet, agent…)"] --> hear["Multi-Output<br>Hear+Tap"]
    hear --> pods["AirPods<br>(you hear it)"]
    hear --> tap["BlackHole 2ch<br>(the tap)"]

    you(("you speak")) --> mic["AirPods mic"]
    mic --> rec["Aggregate: Rec In<br>(mic + playback, one clock)"]
    tap --> rec
    rec --> file["recorder<br>ch1 = your voice<br>ch2–3 = what you heard"]

The commands (fuzzy names are fine — audictl resolves them, and lists
candidates if a name is ambiguous):

# Output side: play to your ears AND into the tap
audictl multi create --name "Hear+Tap" --devices "AirPods,BlackHole 2ch" --primary "AirPods"
audictl default set output "Hear+Tap"

# Input side: mic + tap glued into one recordable device.
# BlackHole is the stable clock; drift-correct the Bluetooth side.
audictl aggregate create --name "Rec In" --devices "AirPods,BlackHole 2ch" \
        --clock "BlackHole 2ch" --drift "AirPods"

# Keep everyone on one rate
audictl rate set "BlackHole 2ch" 48k

Record from “Rec In” with anything — QuickTime and GarageBand can select it
as the input device, or from the terminal:

sox -t coreaudio "Rec In" call.wav          # everything, in sync
sox call.wav mic.wav   remix 1              # your voice
sox call.wav heard.wav remix 2,3            # what you heard

(Or point DialF at it: capture_device: "Rec In" with record_dir and
mix_recording: true in ~/.config/dialf/config.yaml.)

Done recording? Teardown is idempotent — safe to run twice:

audictl default set output "AirPods"
audictl aggregate destroy "Rec In" --if-exists
audictl multi destroy "Hear+Tap" --if-exists

Case 2 — Eval a voice AI agent, no phone required

For developers. Your voice agent runs on (or is reachable from) your Mac —
a browser tab, a softphone, a local process. You want deterministic evals:
play a scripted prompt, capture the reply, measure latency. No cellular in
the loop, so results are reproducible.

Prompt to paste: “Set up the Case 2 rig from this page for my voice
agent, sample rate 44.1k, and run the DialF eval job.”

Two cables, one per direction:

flowchart LR
    dialf["DialF<br>records tx / rx / mix<br>on one clock"] -- "prompt (tx)" --> bh2["BlackHole 2ch"]
    bh2 --> agent["voice agent<br>(browser tab, softphone, process)"]
    agent -- "reply" --> bh16["BlackHole 16ch"]
    bh16 -- "capture (rx)" --> dialf

Preflight, agent-runnable

Every command is idempotent (changed:false on re-run) and exit codes are
meaningful, so this runs unconditionally before every eval:

#!/usr/bin/env bash
set -euo pipefail

# Devices present? (exit 2 = not found)
audictl info "BlackHole 2ch"  --quiet || { echo "install BlackHole 2ch";  exit 1; }
audictl info "BlackHole 16ch" --quiet || { echo "install BlackHole 16ch"; exit 1; }

# Match DialF's configured rate (validates against supported rates, exit 4 if not)
audictl rate set "BlackHole 2ch"  44.1k --quiet
audictl rate set "BlackHole 16ch" 44.1k --quiet

# If the agent app uses system defaults, point them at the cables:
audictl default set input  "BlackHole 2ch"  --quiet
audictl default set output "BlackHole 16ch" --quiet

(If the agent app has its own device picker, select the BlackHole devices
there instead of moving the system defaults.)

DialF config

~/.config/dialf/config.yaml:

audio:
  sample_rate: 44100
  channels: 1
  capture_device: "BlackHole 16ch"      # the agent's output
  playback_device: "BlackHole 2ch"      # the agent's input
  record_dir: ~/Dev/myEval/recordings
  mix_recording: true
  capture_cmd: ["/opt/homebrew/bin/sox", "-q", "-t", "coreaudio", "BlackHole 16ch",
                "-t", "raw", "-b", "16", "-e", "signed-integer",
                "-r", "{rate}", "-c", "{channels}", "-", "remix", "1"]
  playback_cmd: ["/opt/homebrew/bin/sox", "-q", "-V1", "{file}",
                 "-t", "coreaudio", "BlackHole 2ch"]

The eval job

jobs/audio-inject-record.yaml — each turn plays a prompt (recorded as tx),
then waits for the agent to speak and stop (rx):

- type: audio.play
  file: ./samples/EN_Responding_Latency_Samples_loop30_Interval_10s.wav
  description: turn 1 — prompt the agent

- type: log
  message: audio injection eval complete
dialf run jobs/audio-inject-record.yaml    # audio-only: no --device, no phone

Afterward, cross-correlate tx against rx: the gap between tx-end and rx-onset
is the agent’s response latency, on a clean digital path. (For the real
earpiece/codec/acoustic path, keep a phone + sound-card rig; this setup
measures the agent itself.)

Want to listen in while the eval runs?

The agent’s output goes into a virtual cable — silent by default. Tap it the
same way as Case 1:

audictl multi create --name "Eval Monitor" --devices "BlackHole 16ch,MacBook Pro Speakers"

…and have the agent output to “Eval Monitor”: DialF still captures from
BlackHole 16ch, and your speakers get a copy.


Notes for coding agents

If you are Claude, Codex, or another coding agent reading this because a
human pasted it to you: audictl was designed for you. The contract, in
brief — full spec in
SCHEMA.md:

  • Add --json to every command. You get
    {"ok":true,"schemaVersion":1,"changed":…,"data":{…}} on stdout — success
    and error envelopes both, so capture one stream only.
  • Mutations are idempotent and return the resulting state. Re-running a
    satisfied command is ok:true, changed:false, exit 0 — run setup scripts
    unconditionally; don’t pre-check.
  • Exit codes: 2 device not found, 3 ambiguous selector, 4 unsupported
    (includes invalid sample rate, with valid rates in
    error.details.available), 5 CoreAudio error, 6 timeout.
  • On AMBIGUOUS_DEVICE, the error’s details.candidates lists id/uid/name —
    retry with the UID. Store UIDs, never numeric ids (ids change; notably
    aggregates get a new id after every composition edit).
  • audictl list --json is your discovery step; audictl aggregate --json
    shows every aggregate’s members, clock, and drift flags.
  • Clean up with destroy --if-exists / remove --if-absent-ok, and restore
    the user’s default devices when the session ends — leave the machine as
    you found it.
  • Rule of thumb for rigs: BlackHole as the clock source, drift correction
    on Bluetooth/USB sub-devices
    ; set every device in a rig to one sample
    rate before recording.
  • Substitute the user’s real device names: audictl list first, then build.

More scenarios

The two cases compose — tell your agent what you want and it can adapt:

  • Podcast with a remote guest — Case 1 with Zoom/Meet: you on one track,
    guest on the others, edit each side independently.
  • Streaming / screen recording with app audio — tap any app’s sound into
    OBS or QuickTime while still hearing it (Case 1’s output half alone).
  • React / commentary videos — record the video’s audio and your mic
    commentary as separate channels, no desk mixer.
  • Agent-vs-agent — two AI agents talking: one’s output cable is the
    other’s input cable, DialF taps both.
  • CI voice tests — a Mac mini runner where the preflight script is the
    fixture; --private aggregates keep the runner’s device list clean.
  • Whole-house audiomulti create with every AirPlay/HDMI output;
    drift correction keeps rooms in sync.

Whatever the variation, the ask is one sentence: “read
https://github.com/Agora-Build/audictl and wire my Mac to record ___.”

Your audio engineer is in.

DialF: Drive a Real Phone From Your Terminal

A small tool that lets a script place or answer phone calls, talk, listen, and hang up — on a real phone, over a real cellular network.


Why we built this

AI voice agents are everywhere now — and they live and die by latency and audio quality. A second of dead air, a stiff robotic voice, or choppy, fluctuating audio is the difference between “sounds human” and “obviously a bot.” Yet before every release we were measuring those things by hand: dial in, play pre-recorded samples, analyze the gaps, do it again on the next build. It didn’t scale.

What we actually needed was to automate a real phone call. Not a VoIP call. Not a simulator. An actual call on the actual phone and carrier — the kind that rings a normal phone, goes through the normal network, and behaves exactly like a human dialing. So we could:

  • Test phone systems end to end — voice agents, IVRs, call centers, voicemail — the way a real caller experiences them.
  • Run scripted conversations — play a prompt, wait for the other side to finish talking, play the next one.
  • Record both sides cleanly, on one timeline, so we could measure latency (“how long after I speak does the other side respond?”) — as a number, on every build.

The catch: Android won’t let an app record or inject the audio of a cellular call. That path is locked to the system. So a pure software approach is impossible.

DialF’s answer is simple and a little old-school: bridge the call audio through a real USB sound card. The phone does the dialing; a sound card plays into the phone’s mic and listens on its earpiece. Your computer drives the whole thing — and we know you’ll wire your own AI agents up to do the driving.


Why not a programmable 4G module?

It’s the first thing every engineer suggests, and it’s a fair instinct — a cellular module takes a SIM, speaks AT commands, and dials from a script. Cheap, headless, no human in the loop.

A 4G module has its own limitations. It lacks the microphone, speaker, and full audio processing pipeline of a real phone, so it can’t reproduce the complete end-to-end voice path. Instead, it provides only a coarse approximation of the user experience.

A real phone exercises the exact path your users hear—from the microphone and device audio stack, through the cellular network, to the earpiece. That makes it much more representative for measuring latency, audio quality, echo, and other real-world issues. DialF drives a real phone for exactly that reason.


What it does

DialF turns a phone into something you can script:

  • 📞 Make, answer, reject, and hang up calls — on the phone’s own SIM.
  • 💬 Send and read SMS, read the call log and SIM list (dual-SIM aware).
  • 🎛️ Carrier controls — toggle voicemail, run raw MMI/USSD codes.
  • 🗣️ Scripted voice conversations — play audio prompts, and wait for the person to stop talking using voice-activity detection before moving on.
  • 🎙️ Record the call full-duplex — your audio (tx), their audio (rx), and a stereo mix (left = you, right = them), all the same length and sample-aligned (great for latency analysis).

You drive all of it from one command-line tool, or from a small YAML script.


How it works

DialF has two parts that talk to each other, plus a deliberate split between control and audio:

flowchart TB
  subgraph Host["Your computer"]
    CLI["dialf (CLI)"]
    D["dialfd (daemon)"]
    Card["USB sound card"]
  end
  subgraph Android["Android phone"]
    App["DialF Phone app"]
  end

  CLI -->|commands| D
  D <-->|"WiFi · WebSocket"| App
  D <-->|"audio in / out"| Card
  Card <-->|"headset cable"| App
  App -->|"dials / answers on its SIM"| Net(("Cellular network"))
  • Control plane (over WiFi): the dialf CLI sends commands to the dialfd daemon, which relays them to the DialF Phone app over a WebSocket. This is how dial / answer / SMS / hang up happen. No audio travels here.
  • Audio plane (physical): call audio flows through a USB sound card wired to the phone’s headset jack. The card plays into the phone’s microphone and records from its earpiece. The app just routes the call to the wired headset.

Why the split? Because Android blocks call-audio capture in software — so audio has to be bridged physically, never over WiFi.

A scripted call, step by step

sequenceDiagram
  participant CLI as dialf CLI
  participant D as dialfd
  participant P as DialF Phone
  participant F as Far end

  CLI->>D: run call-script.yaml
  D->>P: dial +1...
  P->>F: ringing…
  F-->>P: answers
  P-->>D: call active
  Note over D: call.wait_answered satisfied
  D->>P: play prompt (out the sound card → phone mic)
  P->>F: far end hears the prompt
  F-->>P: spoken reply (phone earpiece → sound card)
  P-->>D: reply audio captured
  Note over D: VAD waits for the reply to finish
  D->>P: play next prompt … then hang up

How to use it

1. Install the CLI (macOS or Linux)

npm install -g @agora-build/dialf
# or:  curl -fsSL https://dl.agora.build/dialf/install.sh | bash

Then start the background daemon:

dialf service install --user      # runs dialfd at login

On a Mac or laptop, keep --user — it runs as you, when you log in (needed so it can reach the sound card and mic). Use plain dialf service install (with sudo) only on a headless Linux server that should start at boot.

2. Install the phone app

Sideload the APK on the Android phone (Android 9+):

Open it, grant phone/SMS permissions, and set it as the default dialer (that’s what lets it place and track calls).

3. Pair them

In the app, enter the same shared key as your dialfd config and tap Start service. The phone finds the daemon automatically on your WiFi (mDNS). Confirm it’s connected:

dialf devices        # your phone should appear

4. Drive it

dialf call dial   <phone> +15551234        # place a call
dialf call hangup <phone>                  # hang up
dialf sms  send   <phone> +15551234 "hi"   # send a text
dialf call list   <phone> --human          # read the call log
dialf --version                            # CLI + daemon versions

5. Script a conversation

Jobs are plain YAML — a list of steps run in order:

- type: call.dial
  number: "+15551234"
- type: call.wait_answered      # wait for a real answer, not a fixed timer
  timeout_ms: 30000
- type: audio.play              # inject a prompt into the call
  file: samples/prompt-en-1.wav
- type: audio.wait_for_speech   # listen until the other side stops talking
  end_timeout_ms: 45000
  silence_duration_ms: 3000
- type: sms.send
  to: "+15551234"
  body: "thanks!"
- type: call.hangup
dialf run call-script.yaml

audio.wait_for_speech is the clever bit: it runs voice-activity detection on the incoming audio, so the script moves on when the person actually finishes speaking — not after a guess.


Recording and latency

If you turn on recording, every call is written as three aligned WAV files:

  • …-tx.wav — what you sent (your prompts), mono
  • …-rx.wav — what the far end said, mono
  • …-mix.wavstereo: left = tx (you), right = rx (them), so the two voices stay separated (swap with mix_channels: rx_tx)

They’re captured on a single clock, so they line up sample-for-sample. That makes latency measurable: cross-correlate tx against rx and the offset is your round-trip delay.

flowchart LR
  TX["tx.wav · your prompt"] --> MIX["mix.wav · stereo · L=tx R=rx"]
  RX["rx.wav · far-end reply"] --> MIX
  TX -.->|"cross-correlate"| RX
  RX --> L["latency = the lag between them"]

Wrapping up

DialF is a thin, scriptable bridge between your terminal and a real phone. The control side is clean software over WiFi; the audio side is honest about hardware — a sound card doing what software isn’t allowed to. Together they let a few lines of YAML place a call, hold a conversation, and hand you a clean recording.

It runs on macOS and Linux, the CLI installs from npm, and the phone app is a sideloadable APK. If you’ve ever wanted to put a real phone call inside a for loop — that’s the idea.


License

DialF is released under the MIT License.

Disclaimer: This tool is strictly for engineering use only and must not be used for any illegal purposes. The user bears all legal consequences arising from its use.

Building WebRTC for Android

ENV
Ubuntu

入门以及下载源码
https://webrtc.org/native-code/development/
https://webrtc.org/native-code/android/

gclient config --name=src https://chromium.googlesource.com/external/webrtc.git
echo "target_os = ['android']" >> .gclient
gclient sync --force
gclient runhooks --force

查看支持的参数列表

gn args --list out/Debug

设置参数

gn gen out/Debug --args='target_os="android" rtc_include_tests=false enable_nocompile_tests=true libyuv_include_tests=false'

开始编译

ninja -C out/Debug 或者 ninja -C out/Release

内存不够的时候就用 -j1 或者 -j2

需要使用项目自带的一些工具的时候需要执行

source ./build/android/envsetup.sh

可能出现的问题

guohai@ubuntu:/home/guohai/WebRTC/src$ ninja -C out/Debug
ninja: Entering directory `out/Debug'
[4/3003] ACTION //base:android_runtime_jni_headers__jni_Runtime(//build/toolchain/android:android_clang_arm)
FAILED: gen/base/android_runtime_jni_headers/base/jni/Runtime_jni.h 
python ../../base/android/jni_generator/jni_generator.py --jar_file ../../third_party/android_tools/sdk/platforms/android-28/android.jar --input_file java/lang/Runtime.class --ptr_type=long --output_dir gen/base/android_runtime_jni_headers/base/jni --includes ../../../../../../../base/android/jni_generator/jni_generator_helper.h
Traceback (most recent call last):
  File "../../base/android/jni_generator/jni_generator.py", line 1405, in <module>
    sys.exit(main(sys.argv))
  File "../../base/android/jni_generator/jni_generator.py", line 1401, in main
    GenerateJNIHeader(input_file, output_file, options)
  File "../../base/android/jni_generator/jni_generator.py", line 1308, in GenerateJNIHeader
    jni_from_javap = JNIFromJavaP.CreateFromClass(input_file, options)
  File "../../base/android/jni_generator/jni_generator.py", line 773, in CreateFromClass
    stderr=subprocess.PIPE)
  File "/usr/lib/python2.7/subprocess.py", line 710, in __init__
    errread, errwrite)
  File "/usr/lib/python2.7/subprocess.py", line 1327, in _execute_child
    raise child_exception
OSError: [Errno 2] No such file or directory
Java 环境变量没有设置好,这里需要 javap 命令
/home/guohai/WebRTC/src/third_party/android_tools/sdk//build-tools/22.0.0/aapt: error while loading shared libraries: libz.so.1: cannot open shared object file: No such file or directory
sudo apt-get install lib32z1

Hello World Android Things

物联网 IoT(Internet of things) 一个听起来高大上,但是实际上是历史悠久东西,但是随着社会/科技的发展(网络,协议,设备等等共同的发展),近些年被正式命名了。

以前开发这类的产品都需要复杂的流程,比如厂商基于某款特定的硬件,移植某个嵌入式的操作系统,然后在上面开发定制化的程序,可能需要懂些底层的东西,比如驱动程序等等,而且运行资源都相对来说很有限。

但是 Google 某一天宣布了一个叫做 Android Things 的东西,好像很多事情都变的简单些了。

这里就不介绍了,直接入门,记录怎么让第一个程序如何跑起来。

1) 硬件设备 RASPBERRY PI 3 MODEL B

我个人比较喜欢这款性价比高的硬件设备,自己买过一些开发板,这个完全不心疼 ^_^

不管是二手的,还是新的,只要型号对的,买个就好了(以前我也很纠结是买原产国还是买国产的,后来就选择买便宜的)

2) 操作系统 Android Things

https://developer.android.com/things/hardware/raspberrypi.html

下载镜像(https://developer.android.com/things/preview/download.html),烧录到 Micro SD Card 上,具体办法网上搜索(我这里旧物利用,翻出来原来 Motorola Milestone 上的一张卡)。制作完毕之后就可以插电开机(USB 供电,HDMI 视频输出,HDMI 也可以提供供电)。

开机之后的画面
at-iot-home

RASPBERRY PI 3 MODEL B 支持无线网络和有线网络,开发调试 adb 支持无线和有线

我这里使用的是 macOS

查看接入的 SD Card 挂载位置

diskutil list
sudo dd bs=1m if=iot_rpi3.img of=/dev/disk3

具体文件名和挂载位置根据实际情况修改

3) 开发程序

https://developer.android.com/things/sdk/samples.html

推出 Android Things 的意图就是物联网会爆发起来(虽然目前还不确切知道什么时候),所以开发程序必须要简单快速。最简单的看下本 Sample 就好了。

本程序和普通的 Android 程序配置上差别不大,就是新建一个标准的 Phone/Tablet 项目就好,主要在 app/build.gradle 和 AndroidManifest.xml 当中有点差别

Jshell 启动错误 build 9-ea+121

Exception in thread "main" java.lang.InternalError: Launching execution engine threw: Failed remote launch: com.sun.jdi.CommandLineLaunch (defaults: home=/Library/Java/JavaVirtualMachines/jdk-9.jdk/Contents/Home, options=, main=, suspend=true, quote=", vmexec=java) -- {home=home=/Library/Java/JavaVirtualMachines/jdk-9.jdk/Contents/Home, options=options=, main=main=jdk.internal.jshell.remote.RemoteAgent 57696, suspend=suspend=true, quote=quote=", vmexec=vmexec=java}
at jdk.jshell.JShell.executionControl(jdk.jshell@9-ea/JShell.java:714)
at jdk.jshell.Unit.classesToLoad(jdk.jshell@9-ea/Unit.java:275)
at jdk.jshell.Eval.lambda$compileAndLoad$15(jdk.jshell@9-ea/Eval.java:580)
at java.util.stream.ReferencePipeline$7$1.accept(java.base@9-ea/ReferencePipeline.java:269)
at java.util.ArrayList$ArrayListSpliterator.forEachRemaining(java.base@9-ea/ArrayList.java:1477)
at java.util.stream.AbstractPipeline.copyInto(java.base@9-ea/AbstractPipeline.java:484)
at java.util.stream.AbstractPipeline.wrapAndCopyInto(java.base@9-ea/AbstractPipeline.java:474)
at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(java.base@9-ea/ReduceOps.java:913)
at java.util.stream.AbstractPipeline.evaluate(java.base@9-ea/AbstractPipeline.java:234)
at java.util.stream.ReferencePipeline.collect(java.base@9-ea/ReferencePipeline.java:511)
at jdk.jshell.Eval.compileAndLoad(jdk.jshell@9-ea/Eval.java:581)
at jdk.jshell.Eval.declare(jdk.jshell@9-ea/Eval.java:441)
1 127.0.0.1 Hais-MacBook-Pro
at jdk.jshell.Eval.processMethod(jdk.jshell@9-ea/Eval.java:372)
at jdk.jshell.Eval.eval(jdk.jshell@9-ea/Eval.java:127)
at jdk.jshell.JShell.eval(jdk.jshell@9-ea/JShell.java:393)
at jdk.internal.jshell.tool.JShellTool.processCompleteSource(jdk.jshell@9-ea/JShellTool.java:2114)
at jdk.internal.jshell.tool.JShellTool.processSource(jdk.jshell@9-ea/JShellTool.java:2102)
at jdk.internal.jshell.tool.JShellTool.processSourceCatchingReset(jdk.jshell@9-ea/JShellTool.java:789)
at jdk.internal.jshell.tool.JShellTool.run(jdk.jshell@9-ea/JShellTool.java:769)
at jdk.internal.jshell.tool.JShellTool.startUpRun(jdk.jshell@9-ea/JShellTool.java:706)
at jdk.internal.jshell.tool.JShellTool.resetState(jdk.jshell@9-ea/JShellTool.java:663)
at jdk.internal.jshell.tool.JShellTool.start(jdk.jshell@9-ea/JShellTool.java:483)
at jdk.internal.jshell.tool.JShellTool.start(jdk.jshell@9-ea/JShellTool.java:462)
at jdk.internal.jshell.tool.JShellTool.main(jdk.jshell@9-ea/JShellTool.java:452)
Caused by: java.lang.InternalError: Failed remote launch: com.sun.jdi.CommandLineLaunch (defaults: home=/Library/Java/JavaVirtualMachines/jdk-9.jdk/Contents/Home, options=, main=, suspend=true, quote=", vmexec=java) -- {home=home=/Library/Java/JavaVirtualMachines/jdk-9.jdk/Contents/Home, options=options=, main=main=jdk.internal.jshell.remote.RemoteAgent 57696, suspend=suspend=true, quote=quote=", vmexec=vmexec=java}
at jdk.internal.jshell.jdi.JDIConnection.reportLaunchFail(jdk.jshell@9-ea/JDIConnection.java:353)
at jdk.internal.jshell.jdi.JDIConnection.launchTarget(jdk.jshell@9-ea/JDIConnection.java:319)
at jdk.internal.jshell.jdi.JDIConnection.open(jdk.jshell@9-ea/JDIConnection.java:120)
at jdk.internal.jshell.jdi.JDIEnv.init(jdk.jshell@9-ea/JDIEnv.java:49)
at jdk.internal.jshell.jdi.JDIExecutionControl.jdiGo(jdk.jshell@9-ea/JDIExecutionControl.java:425)
at jdk.internal.jshell.jdi.JDIExecutionControl.start(jdk.jshell@9-ea/JDIExecutionControl.java:95)
at jdk.jshell.JShell.executionControl(jdk.jshell@9-ea/JShell.java:712)
... 23 more
Caused by: com.sun.jdi.connect.VMStartException: VM initialization failed for: /Library/Java/JavaVirtualMachines/jdk-9.jdk/Contents/Home/bin/java -Xdebug -Xrunjdwp:transport=dt_socket,address=Hais-MacBook-Pro:57697,suspend=y jdk.internal.jshell.remote.RemoteAgent 57696
at com.sun.tools.jdi.AbstractLauncher$Helper.launchAndAccept(jdk.jdi@9-ea/AbstractLauncher.java:193)
at com.sun.tools.jdi.AbstractLauncher.launch(jdk.jdi@9-ea/AbstractLauncher.java:132)
at com.sun.tools.jdi.SunCommandLineLauncher.launch(jdk.jdi@9-ea/SunCommandLineLauncher.java:225)
at jdk.internal.jshell.jdi.JDIConnection.launchTarget(jdk.jshell@9-ea/JDIConnection.java:312)
... 28 more

https://bugs.openjdk.java.net/browse/JDK-8131029

这个问题在新版的已经被修复了,但是如果碰到了,可以修改 /etc/hosts 来绕过(增加一个本地计算机名字的 loop ip)

本地计算机名字可以用

uname -n

来查看

参考这个文章找到的 workaround

小结二零一五

虽然都是流水账(这年终总结也做的太晚了T_T),但是还是要记录一下,还是分 2 个部分。

工作上有喜也有忧,总之创业不是个容易的事情,“大众创业万众创新”看起来很霸气,但是你看看今年死掉多少创业公司就知道了,还好我们还算幸运,公司人力和业务在这一年都有了比较大的进展,但是距离目标还是很遥远。自己负责的这一块整体进展还不令人满意(自己的角色也从一个不停写代码的人转变为写代码 + 维护整体项目),但是今年应该会有些努力尝试的方向和自由度,另外也需要更集中精力,因为不免杂事很多,需要花比较多的时间来处理,效率下降。不过总之我们都还是走在正确的路上。

生活上今年膝盖受伤(膝关节脂肪垫磨损)是最大的障碍了,医生劝告自己修养,所以很多运动都无法进行,非常感谢这期间同事一段时间内几乎每天帮我带外卖,希望 2016 年能恢复到可以自由的玩所有各种运动(千万是不要自己作死,哈哈哈)。能和喜欢的人在一起也很开心,虽然经历了千辛万苦,期待我们的未来美好,当然先努力实现我们的一些小愿望吧,一步一步朝前走。

还有一件重要的事情是时间上能安排合理,需要看书,写代码,写博客,这地方基本已经处于半荒废状态了。
忙,不要把自己忙的找不到方向了

P.S. 目前我们正在做的是互联网上提供有保障的实时音视频传输方案,Powering Real-Time Communications,agora.io,广告就不多做啦

独立编译 Skia for Android

最近想了解下 Skia 相关的东西,想利用其中的一些 API 来做做优化,所以打算独立编译一个版本试试看。

https://skia.org/user/quick/android

使用的代码版本

commit 81bdbf8bed8b739c2b65ac576e89d0258276e6dc
Author: caryclark <[email protected]>
Date:   Wed Oct 21 04:16:19 2015 -0700

编译环境

Ubuntu 14.04.2

直接按照官方说明就可以编译出来,我这里是不想去下载一遍 NDK,所以进行了点改动。

http://dl.google.com/android/ndk/android-ndk-r10e-linux-x86_64.bin

如果机器上已经安装过对应版本的 NDK,可以修改以下文件直接生成 TOOLCHAIN(这个步骤不是必须的)

/mnt/extra/skia/platform_tools/android/bin/utils/setup_toolchain.sh
 function default_toolchain() {
-  TOOLCHAINS=${SCRIPT_DIR}/../toolchains
+  TOOLCHAINS=/home/ubuntu/dev
 
   ANDROID_ARCH=${ANDROID_ARCH-arm}
   LLVM=3.6
@@ -50,19 +50,13 @@ function default_toolchain() {
   exportVar ANDROID_TOOLCHAIN "${TOOLCHAINS}/${TOOLCHAIN}/bin"
 
   if [ ! -d "$ANDROID_TOOLCHAIN" ]; then
-    mkdir -p $TOOLCHAINS
     pushd $TOOLCHAINS
-    curl -o $NDK.bin https://dl.google.com/android/ndk/android-ndk-$NDK-$HOST-x86_64.bin
-    chmod +x $NDK.bin
-    ./$NDK.bin -y
     ./android-ndk-$NDK/build/tools/make-standalone-toolchain.sh \
         --arch=$ANDROID_ARCH    \
         --llvm-version=$LLVM    \
         --platform=android-$API \
         --install_dir=$TOOLCHAIN
     cp android-ndk-$NDK/prebuilt/android-$ANDROID_ARCH/gdbserver/gdbserver $TOOLCHAIN
-    rm $NDK.bin
-    rm -rf android-ndk-$NDK
     popd
   fi

生成过一次 TOOLCHAIN 之后也可以把

export ANDROID_TOOLCHAIN=/home/ubuntu/dev/arm-r10e-14/bin
export PATH=$ANDROID_TOOLCHAIN:$PATH

手动加在到配置文件里面去(这个步骤不是必须的)

./platform_tools/android/bin/android_ninja -d nexus_5

然后就是等待编译,如果中途编译 APK 的时候却少一些特定版本的 Build Tool 的时候修改下 App 当中使用版本就好了,或者也可以去更新代码当中对应的版本
App 代码位于

/mnt/extra/skia/platform_tools/android/apps/

编译完成之后就可以在

/mnt/extra/skia/out/config/android-nexus_5/Debug

下看到 so 了

android.util.Pair 引起的崩溃

博客好久没有更新过了。
一直都觉得自己没啥时间 囧囧

创业开始一直都在负责 App 相关的工作。
早上例行看了下昨日统计,崩溃率暴涨,但是就维持在 4 个用户,一看 Android 版本,都是 4.0.4,
心想肯定尼玛有碰到了不该用的 API。

FATAL EXCEPTION: h-262 262
PID: 2610
java.lang.NullPointerException
at android.util.Pair.hashCode(Pair.java:63)
at java.lang.Object.toString(Object.java:332)
at java.lang.StringBuilder.append(StringBuilder.java:202)
at java.util.AbstractMap.toString(AbstractMap.java:448)
at java.lang.StringBuilder.append(StringBuilder.java:202)
......
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:137)
at cc.beckon.service.a.h.run(l:159)

看了下最新的 android.util.Pair 代码,似乎没有什么问题,然后追溯这个文件的修改历史
Screen Shot 2015-10-06 at 12.34.01 PM
对于为 Null 的值在低版本的 Pair 上确实无法处理,回过头来看,在这条案例上没有测试到就直接上了,该打。

小结二零一四

看了下 2014 年的目标 小结二零一三,很多都没有完成。。。所以 2015 年的目标就是把 2014 年的完成[我知道你们会不相信 @_@]。

2014
中秋回家看了一次家人,然后还算经常地给他们通电话。。。

在一家 Vans 店看到别人玩滑板的视屏觉得很酷,然后自己买了块板开始玩,不过时间有限,努力训练也没有别人多,所以技术自然也不是特别好,处于一个需要认真训练才能跨过的坎,反正慢慢玩呗,有空晚上去滑滑,看别人滑滑,跟着大部队刷刷街

上半年还比较勤的去玩篮球,下半年就只能呵呵了

有一个事情还是算好的,现在大部分时间可以坐直了工作(以前都是弯着腰),形成了习惯,不过不知道对脊柱有没有什么不良影响

其它大部分时间应该都是工作,吃饭,睡觉,一直处于忙碌状态