音频变速爬过的坑

sonic和soundtouch两个库都用过。用法差不多。项目上线用的是soundtouch。做视频编辑,本来opengl那块就够折腾了,音频这块更是艰难。难点在于变速和混音。

音频变速呢,比视频变速复杂多了,一处理不好就杂音呲呲呲。记录一下soundtouch的攻克历程。

故事的开始

搜索了一堆资料,典型文章《当一个 Android 开发玩抖音玩疯了之后(一)》等等,基本跑通了功能,上线了。最开始抄的哪个库,在自己笔记本电脑上(找到补上)。

出现问题

1.升速效果很好,降速不生效?

2.双声道的效果很好,单声道的变调得很滑稽?

3.偶现降速会挂掉?

解决问题

1.有关降速不生效的问题,应该是8位转16位没有处理好。

SoundTouch支持两种类型sample类型:16位有符号整数和32位浮点数,默认使用的是32为浮点数,改为了16位有符号整型。在java中操作文件并使用mediacodec解码时使用的是byte数组流,这就涉及到如何将8位的java byte数组转换为C++中16位有符号整型进行运算。
一开始使用了jni强转,reinterpreter_cast,当然不对...
然后想在java中把byte先转为short,jni中使用jshort传参,c++中接收的short就是16位的啦,byte和short相互转换的代码如下:

 public static short[] bytesToShort(byte[] bytes) {
        if(bytes==null){
            return null;
        }
        short[] shorts = new short[bytes.length/2];
        ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer().get(shorts);
        return shorts;
    }
    public static byte[] shortToBytes(short[] shorts) {
        if(shorts==null){
            return null;
        }
        byte[] bytes = new byte[shorts.length * 2];
        ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer().put(shorts);
        return bytes;
    }

发现降速还是没有生效,声音变快效果很好,但是不能变慢。只能去C++代码中寻找答案,毕竟java处理音视频数据是不得力的....发现人家是这么做转换的:

static void convertInput(jbyte* input, float* output, const int BUFF_SIZE,
        int bytesPerSample) {
    switch (bytesPerSample) {
    case 1: {
        unsigned char *temp2 = (unsigned char*) input;
        double conv = 1.0 / 128.0;
        for (int i = 0; i < BUFF_SIZE; i++) {
            output[i] = (float) (temp2[i] * conv - 1.0);
        }
        break;
    }
    case 2: {
        short *temp2 = (short*) input;
        double conv = 1.0 / 32768.0;
        for (int i = 0; i < BUFF_SIZE; i++) {
            short value = temp2[i];
            output[i] = (float) (value * conv);
        }
        break;
    }
    case 3: {
        char *temp2 = (char *) input;
        double conv = 1.0 / 8388608.0;
        for (int i = 0; i < BUFF_SIZE; i++) {
            int value = *((int*) temp2);
            value = value & 0x00ffffff;             // take 24 bits
            value |= (value & 0x00800000) ? 0xff000000 : 0; // extend minus sign bits
            output[i] = (float) (value * conv);
            temp2 += 3;
        }
        break;
    }
    case 4: {
        int *temp2 = (int *) input;
        double conv = 1.0 / 2147483648.0;
        assert(sizeof(int) == 4);
        for (int i = 0; i < BUFF_SIZE; i++) {
            int value = temp2[i];
            output[i] = (float) (value * conv);
        }
        break;
    }
    }
}

bytesPerSample是什么,是采样点的字节数,噢,我们没有考虑这个参数...

2.单声道声音变怪异
使用了SoundTouch的setChannels设置了声道数,但还是不对?
再检查一下putSamples和getSamples方法

extern "C"
JNIEXPORT void JNICALL Java_com_netease_glsoundlib_SoundTouch_putSamples
        (JNIEnv *env, jobject thiz, jshortArray samples, jint length, jlong objectPtr) {
    SoundTouch *soundTouch = (SoundTouch *) objectPtr;
    // 转换为本地数组
    jshort *input_samples = env->GetShortArrayElements(samples, NULL);
    soundTouch->putSamples(input_samples, length);
    // 释放本地数组(避免内存泄露)
    env->ReleaseShortArrayElements(samples, input_samples, 0);
}

length传的是什么,是一个chunk的大小,固定为4096,好像不对吧,官方的解释是“Adds 'numSamples' pcs of samples from the 'samples' memory position into the input of the object.”其参数名为“nSamples”,官方是这么计算的:

    int nSamples = BUFF_SIZE / channels;
       ...
    const int BUFF_SIZE = length / bytesPerSample;

而length才是输入数据块的大小,即sample.length

3.偶现降速会挂掉
问题应该是降速后从soundtouch接收的数据块较大,数组溢出,这块代码不对...

    soundTouch.putBytes(resultByteArray);
    while (true) {
                            byte[] modified = new byte[4096];
                            int count = soundTouch.getBytes(modified);
                            if (count > 0) {
                                bufferedOutputStream.write(modified);
                            } else {
                                break;
                            }
                     }
               

总结

1.不要迷信别人的博客或者demo,特别是一些做培训的人放在github上虽然星星多流量多,但是不靠谱,最靠谱的是官方源码和官方文档。
2.自己整理的demo
https://github.com/meiliqin/AndroidSoundTouch.git

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,793评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,567评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,342评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,825评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,814评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,680评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,033评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,687评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,175评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,668评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,775评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,419评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,020评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,206评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,092评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,510评论 2 343

推荐阅读更多精彩内容