女儿:爸爸,今天我看到隔壁的小朋友在玩变声话筒,可以把自己的声音变成萝莉声、魔兽声,可好玩了!我也想玩!
爸爸:嗯… 这个容易,马上给你做一个。
<思路>
- 先把声音从麦克风录进来,过一个音调调节器,再实时输出到喇叭即可。
- 由于录音和播放大部分时间在等I/O,如果放在同一个线程里做,那么等待playback I/O的时候无法capture,等待capture I/O的时候又无法playbacak,这样就很容易overrun或underrun。故把capture和playback放在两个线程做,用FIFO传递数据。
<代码实现>
利用lark (https://gitee.com/wksuper/lark-release),可以轻松实现。
MyEngineerDaddy_1.cpp
#include <lark/lark.h>
#include <klogging.h>
#if defined(__APPLE__)
#define SUFIX ".dylib"
#elif defined(_WIN32)
#define SUFIX ".dll"
#else
#define SUFIX ".so"
#endif
int main()
{
const unsigned int rate = 16000;
const lark::SampleFormat format = lark::SampleFormat_FLOAT;
const unsigned int chNum = 1;
const unsigned int frameDuration_ms = 20;
const lark::samples_t frameSizeInSamples = frameDuration_ms * rate / 1000;
lark::FIFO *fifo = lark::Lark::Instance().NewFIFO(
rate,
lark::SamplesToBytes(format, chNum, 1),
frameSizeInSamples * 8);
if (!fifo) {
KLOGE("Failed to new a FIFO");
return -1;
}
// Create the playback route named RouteA
lark::Route *playbackRoute = lark::Lark::Instance().NewRoute("RouteA");
if (!playbackRoute) {
KLOGE("Failed to create playbackRoute");
return -1;
}
// Create RouteA's blocks
const char *soFileName = "libblkstreamin" SUFIX;
lark::Parameters args;
args.push_back(std::to_string(rate));
args.push_back(std::to_string(format));
args.push_back(std::to_string(chNum));
lark::DataProducer *producer = fifo;
producer->SetBlocking(true);
args.push_back(std::to_string((unsigned long)producer));
lark::Block *blkStreamIn = playbackRoute->NewBlock(soFileName, true, false, args);
if (!blkStreamIn) {
KLOGE("Failed to new a block from %s", soFileName);
return -1;
}
soFileName = "libblksoundtouch" SUFIX;
lark::Block *blkSoundTouch = playbackRoute->NewBlock(soFileName, false, false);
if (!blkSoundTouch) {
KLOGE("Failed to new a block from %s", soFileName);
return -1;
}
soFileName = "libblkpaplayback" SUFIX;
lark::Block *blkPlayback = playbackRoute->NewBlock(soFileName, false, true);
if (!blkPlayback) {
KLOGE("Failed to new a block from %s", soFileName);
return -1;
}
// Create RouteA's links
if (!playbackRoute->NewLink(rate, format, chNum, frameSizeInSamples, blkStreamIn, 0, blkSoundTouch, 0)) {
KLOGE("Failed to new a link");
return -1;
}
if (!playbackRoute->NewLink(rate, format, chNum, frameSizeInSamples, blkSoundTouch, 0, blkPlayback, 0)) {
KLOGE("Failed to new a link");
return -1;
}
// Create the capture route named RouteB
lark::Route *captureRoute = lark::Lark::Instance().NewRoute("RouteB");
if (!captureRoute) {
KLOGE("Failed to create captureRoute");
return -1;
}
soFileName = "libblkpacapture" SUFIX;
lark::Block *blkCapture = captureRoute->NewBlock(soFileName, true, false);
if (!blkCapture) {
KLOGE("Failed to new a block from %s", soFileName);
return -1;
}
soFileName = "libblkstreamout" SUFIX;
args.clear();
args.push_back(std::to_string(rate));
args.push_back(std::to_string(format));
args.push_back(std::to_string(chNum));
lark::DataConsumer *consumer = fifo;
consumer->SetBlocking(true);
args.push_back(std::to_string((unsigned long)consumer));
lark::Block *blkStreamOut = captureRoute->NewBlock(soFileName, false, true, args);
if (!blkStreamOut) {
KLOGE("Failed to new a block from %s", soFileName);
return -1;
}
if (!captureRoute->NewLink(rate, format, chNum, frameSizeInSamples, blkCapture, 0, blkStreamOut, 0)) {
KLOGE("Failed to new a link");
return -1;
}
// Start() or Stop()
if (captureRoute->Start() < 0) {
KLOGE("Failed to start captureRoute");
return -1;
}
if (playbackRoute->Start() < 0) {
KLOGE("Failed to start playbackRoute");
return -1;
}
while (1) {
KLOGA("Press 'q' to exit");
again:
char c;
if (scanf("%c", &c) != 1)
break;
if (c == '\n')
goto again;
else if (c == 'q')
break;
}
fifo->Shutdown();
// Automatically release resources
return 0;
}
<编译运行>
$ g++ -lklogging -llark MyEngineerDaddy_1.cpp -o MyEngineerDaddy_1
带上耳机,
$ ./MyEngineerDaddy_1
06-13 12:39:26.297680 I | Created RouteA
06-13 12:39:26.297949 I | RouteA: blkstreamin_0: Created
06-13 12:39:26.298306 I | RouteA: blksoundtouch_0: Created
06-13 12:39:26.298985 I | RouteA: blkpaplayback_0: Created
06-13 12:39:26.299122 I | RouteA: lnk_0: Created blkstreamin_0(O00) --> (I00)blksoundtouch_0
06-13 12:39:26.479154 I | RouteA: lnk_1: Created blksoundtouch_0(O00) --> (I00)blkpaplayback_0
06-13 12:39:26.479491 I | Created RouteB
06-13 12:39:26.480217 I | RouteB: blkpacapture_0: Created
06-13 12:39:26.480838 I | RouteB: blkstreamout_0: Created
06-13 12:39:26.502480 I | RouteB: lnk_0: Created blkpacapture_0(O00) --> (I00)blkstreamout_0
06-13 12:39:26.502647 I | RouteB: Starting
06-13 12:39:26.506586 I | RouteB: Started
06-13 12:39:26.506700 I | RouteB: Status is RUNNING
06-13 12:39:26.510308 I | RouteA: Starting
06-13 12:39:26.513085 I | RouteA: Started
06-13 12:39:26.513211 I | RouteA: Status is RUNNING
06-13 12:39:26.513326 A | Press 'q' to exit
“喂喂喂”,可以从耳机里直接听到说话声了。
看下路由运行状态:
$ lkdb status
RouteB is RUNNING, 374 frames processed OK, 0 error frame
blkpacapture_0
(O00) --> lnk_0 16000Hz FLOAT_LE 1ch 320samples/frame
blkstreamout_0
(I00) <-- lnk_0 16000Hz FLOAT_LE 1ch 320samples/frame
RouteA is RUNNING, 374 frames processed OK, 0 error frame
blkstreamin_0
(O00) --> lnk_0 16000Hz FLOAT_LE 1ch 320samples/frame
blksoundtouch_0
(I00) <-- lnk_0 16000Hz FLOAT_LE 1ch 320samples/frame
(O00) --> lnk_1 16000Hz FLOAT_LE 1ch 1040samples/frame
blkpaplayback_0
(I00) <-- lnk_1 16000Hz FLOAT_LE 1ch 1040samples/frame
fifo_0 0/2560 (0%)
变萝莉声,只需要调节blksoundtouch_0
的pitch参数。
$ lkdb setparam RouteA blksoundtouch_0 1 1.4
注解:此命令表示用lkdb工具跟运行时的MyEngineerDaddy_1进程实时通讯。
setparam
表示对路由上的某个块设定参数;
RouteA blksoundtouch_0
表示对名叫RouteA
的路由上的名叫blksoundtouch_0
的块设定参数;
1
表示调节pitch
1.4
表示把pitch升高到1.4倍。(默认为1.0)
再变魔兽声:
$ lkdb setparam RouteA blksoundtouch_0 1 0.6
爸爸:搞定!
女儿:哈哈,太好玩了!谢谢爸爸!