我的工程师爸爸 - 音频应用DIY系列之一:变声话筒

女儿:爸爸,今天我看到隔壁的小朋友在玩变声话筒,可以把自己的声音变成萝莉声、魔兽声,可好玩了!我也想玩!
爸爸:嗯… 这个容易,马上给你做一个。

<思路>

  • 先把声音从麦克风录进来,过一个音调调节器,再实时输出到喇叭即可。
  • 由于录音和播放大部分时间在等I/O,如果放在同一个线程里做,那么等待playback I/O的时候无法capture,等待capture I/O的时候又无法playbacak,这样就很容易overrun或underrun。故把capture和playback放在两个线程做,用FIFO传递数据。
    图1-1

<代码实现>

利用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

爸爸:搞定!

女儿:哈哈,太好玩了!谢谢爸爸!

lark项目地址:https://gitee.com/wksuper/lark-release

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值