美文网首页Android开发
音频特征(5):改变音调

音频特征(5):改变音调

作者: 广州小程 | 来源:发表于2019-06-28 11:16 被阅读1次

改变声音的音调,有时候很好玩,像Talking Tom里面的那只猫一样,尖起声音学人说话很逗人。

本文介绍如何控制音频的音调、节拍或播放速率。

是徒手直接修改音频数据吗?现在不是,现在是借助soundtouch来达到这种效果,至于徒手的办法后续再讲。

soundtouch,一个开源的音效处理项目(用c++编写),可以更改音频的音调、播放速率、节拍等特征。大概的思路是这样的,先解码音频,得到pcm数据,再通过soundtouch来修改pcm数据,最后压缩为常见格式的音频。

小程上传了一个变调后的音频文件,你可以关注“广州小程”微信公众号,再来试听下面的音频文件(如果博客不支持外链音频,那就听不了了):
变调效果

然后,小程介绍下如何写代码来实现这个变调的功能。

先使用FFmpeg来解码,得到pcm后调用soundtouch来处理,最后使用FFmpeg命令把pcm编码成mp3。

演示demo的目录结构是这样的:


演示demo的目录结构

先贴上代码(change_pcm_pitch.cpp的内容),之后再简单介绍soundtouch的调用:

extern "C" {
#include "ffmpeg/include/libavcodec/avcodec.h"
#include "ffmpeg/include/libavformat/avformat.h"
#include "ffmpeg/include/libswresample/swresample.h"
#include "ffmpeg/include/libavutil/samplefmt.h"
}
#include "SoundTouch.h"
using namespace soundtouch;

// 解码并变调
void change_pcm_pitch(const char* filepath) {
    av_register_all();
    av_log_set_level(AV_LOG_DEBUG);
    AVFormatContext* formatContext = avformat_alloc_context();
    AVCodecContext* codecContext = NULL;
    int status = 0;
    bool success = false;
    int audioindex = -1;
    status = avformat_open_input(&formatContext, filepath, NULL, NULL);
    if (status == 0) {
        status = avformat_find_stream_info(formatContext, NULL);
        if (status >= 0) {
            for (int i = 0; i < formatContext->nb_streams; i ++) {
                if (formatContext->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO) {
                    audioindex = i;
                    break;
                }   
            }
            if (audioindex > -1) {
                codecContext = formatContext->streams[audioindex]->codec;
                AVCodec* codec = avcodec_find_decoder(codecContext->codec_id);
                if (codec) {
                    status = avcodec_open2(codecContext, codec, NULL);
                    if (status == 0) {
                        success = true; 
                    }
                }
            }
        }
    }
    if (success) {
        av_dump_format(formatContext, 0, filepath, false);
        av_log(NULL, AV_LOG_DEBUG, "format and decoder sucessful, and now in decoding each frame\n");
        printf("sample_rate=%d, channels=%d\n", codecContext->sample_rate, codecContext->channels);
        SoundTouch* soundtouch = new SoundTouch();
        printf("soundtouch version=%s\n", soundtouch->getVersionString());
        soundtouch->setSampleRate(codecContext->sample_rate);
        soundtouch->setChannels(codecContext->channels);
        soundtouch->setTempo(0.5);  // tempo,播放节奏,1.0为正常节奏,大于1.0加快,小于1.0变慢,pcm的体积随之变化
        soundtouch->setRate(3.0);  // rate,播放速率,1.0为正常速度;单设置这个时,除了影响播放速度,还会影响到音调
        soundtouch->setPitch(0.5);   // pitch,音调,1.0为正常音调;这个设置并不会影响到时长
        AVFrame* frame = av_frame_alloc();
        SwrContext* swr = NULL;
        int gotframe = 0;
        char outfile[512] = {0};
        strcpy(outfile, filepath);
        strcat(outfile + strlen(outfile), ".pcm");
        FILE* file = fopen(outfile, "wb");
        if (file) {
            while (true) {
                AVPacket packet;
                av_init_packet(&packet);
                status = av_read_frame(formatContext, &packet);
                if (status < 0) {
                    if (status == AVERROR_EOF) {
                        av_log(NULL, AV_LOG_DEBUG, "read end for file\n");
                        break;
                    }
                    else {
                        av_packet_unref(&packet);
                    }
                }
                else {
                    if (packet.stream_index == audioindex) {
                        int srcCount = packet.size;
                        while (srcCount > 0) {
                            int decodedcount = avcodec_decode_audio4(codecContext, frame, &gotframe, &packet);
                            if (decodedcount < 0) {
                                av_log(NULL, AV_LOG_DEBUG, "decode failed, perhaps not enough data\n");
                                break;
                            }
                            if (gotframe > 0) {
                                // resample 
                                int targetchannel = 2;
                                int targetsrate = 44100;
                                int targetfmt = AV_SAMPLE_FMT_S16;
                                bool needresample = false;
                                if (av_frame_get_channels(frame) != targetchannel || frame->sample_rate != targetsrate || frame->format != targetfmt) {
                                    needresample = true;    
                                }
                                if (needresample) {
                                    if (swr == NULL) {
                                        uint64_t in_channel_layout = av_get_default_channel_layout(av_frame_get_channels(frame));
                                        uint64_t out_channel_layout = av_get_default_channel_layout(targetchannel);
                                        int inSamplerate = frame->sample_rate;
                                        swr = swr_alloc_set_opts(NULL,
                                                out_channel_layout, (enum AVSampleFormat )AV_SAMPLE_FMT_S16, targetsrate,
                                                in_channel_layout, (enum AVSampleFormat)frame->format, inSamplerate, 0, NULL);
                                        int ret = swr_init(swr);
                                        if (ret != 0) {
                                            printf("swr_init failed: ret=%d\n", ret);
                                        }
                                    }
                                    if (swr) {
                                        if (frame->extended_data && frame->data[0] && frame->linesize[0] > 0) {
                                            int out_size = av_samples_get_buffer_size(NULL, targetchannel, frame->nb_samples, (enum AVSampleFormat)targetfmt, 0);
                                            void* out_buffer = av_malloc(out_size);
                                            if (out_buffer) {
                                                int convertSamples = swr_convert(swr, (uint8_t**)(&out_buffer), frame->nb_samples, 
                                                        (const uint8_t**)frame->extended_data, frame->nb_samples);
                                                int len = convertSamples * targetchannel * av_get_bytes_per_sample((enum AVSampleFormat)targetfmt);
                                                int samplecount = convertSamples;
                                                soundtouch->putSamples((SAMPLETYPE*)out_buffer, samplecount);
                                                int bufsize = samplecount * frame->channels * sizeof(short);
                                                unsigned char* buf = (unsigned char*)malloc(bufsize);
                                                int gotsamplecount = soundtouch->receiveSamples((SAMPLETYPE*)buf, samplecount);
                                                printf("soundtouch receiveSamples after resample:gotsamplecount=%d bufsize=%d sizeof(SAMPLETYPE)=%lu\n", gotsamplecount, bufsize, sizeof(SAMPLETYPE));
                                                if (gotsamplecount) {
                                                    fwrite(buf, gotsamplecount * frame->channels * sizeof(short), 1, file);
                                                }
                                                free(buf);  
                                                av_free(out_buffer);
                                            }
                                        }
                                    }
                                }
                                else {
                                    int samplecount = frame->nb_samples;
                                    soundtouch->putSamples((SAMPLETYPE*)frame->data[0], samplecount);
                                    int bufsize = samplecount * frame->channels * sizeof(short);
                                    unsigned char* buf = (unsigned char*)malloc(bufsize);
                                    int gotsamplecount = soundtouch->receiveSamples((SAMPLETYPE*)buf, samplecount);
                                    printf("soundtouch receiveSamples:gotsamplecount=%d bufsize=%d sizeof(SAMPLETYPE)=%lu\n", gotsamplecount, bufsize, sizeof(SAMPLETYPE));
                                    if (gotsamplecount) {
                                        fwrite(buf, gotsamplecount * frame->channels * sizeof(short), 1, file);
                                    }
                                    free(buf);  
                                }
                            }
                            srcCount -= decodedcount;
                        }
                    }
                }
                av_packet_unref(&packet);
            }   
            fclose(file);
        }
        av_frame_free(&frame);
        delete soundtouch;
        if (swr) {
            swr_free(&swr);
        }
    }
    avformat_free_context(formatContext);
}

// 压缩成mp3。保证FFmepg支持mp3编码即可(使用lamemp3),当然也可以编码成其它格式
// 小程安装了多个不同特性的FFmpeg,这里指定一个能编码mp3的ffmpeg
const char* FFMPEGEXE = "/usr/local/Cellar/ffmpeg/2.6.2/bin/ffmpeg";  
const int SAMPLE_RATE = 44100;
const int CHANNELS = 2;
const int BITRATE = 128;
const int BUF_LEN = 1024;

// 编码
void encode(const char* srcfile, const char* outfile) {
    char buf[BUF_LEN] = {0};
    sprintf(buf, "%s -ar %d -ac %d -f s16le -i %s -ar %d -ac %d -b:a %dK -y %s", FFMPEGEXE, SAMPLE_RATE, CHANNELS, srcfile, SAMPLE_RATE, CHANNELS, BITRATE, outfile);
    system(buf);
}

int main(int argc, const char *argv[])
{
    const char filepath[] = "test2.mp3";    
    change_pcm_pitch(filepath);  // xxx.xx.pcm create
    encode("test2.mp3.pcm", "out.mp3");

    return 0;
}

上面的demo直接使用了soundtouch的源码来实现音效变化。

soundtouch初始化:


soundtouch初始化

soundtouch作用于pcm:


soundtouch处理pcm

需要注意,soundtouch并没有解码功能,它假设调用层已经有pcm数据。

小程这里没有考虑音效处理的性能,实际上对于实时播放,音效的性能是要关注的因素,需要避免音效过于耗时而导致播放卡顿,这是另外一个话题了。

总结一下,本文介绍了soundtouch的调用,实现了音频变调等效果。soundtouch的接口调用比较简单,解码与重采样的处理工作反而更多一些。


hello

相关文章

  • 音频特征(5):改变音调

    改变声音的音调,有时候很好玩,像Talking Tom里面的那只猫一样,尖起声音学人说话很逗人。 本文介绍如何控制...

  • iOS音频变速变调研究

    最近在做iOS音频播放相关的项目,需要处理声音的播放快慢同时不能改变音频的音调,这个倒是简单,系统提供了一个播放类...

  • FFmpeg实践记录五:音频基础知识

    音频数据流 声音的三要素 音调(音频的快慢 男生->女生>儿童) 音量(振动的幅度) 音色(谐波)音调音量谐波 模...

  • 声现象(二)

    第二节 声音的特征(重点) 1.音调:声音的 高低 叫音调.声音的音调与声源的振动频率有关,声源的振动频...

  • iOS音视频详解

    音频基础 1.音频三要素 音调:就是音频 , 男生 -> 女生 -> 儿童 音量:震动的幅度 音色:它与材质有很大...

  • 音频基础知识

    一、声音的三要素 1.音调 人耳对声音高低的感觉称为音调(也叫音频)。音调主要与声波的频率有关。声波的频率高,则音...

  • 日语学习 Day 13:发音16课

    音调学习 1、什么是音调? 日语中的音调以高低来区分:音调不同单词的意思有可能也随之改变。 举例: 2、0调:第一...

  • 音频视频基础知识

    音频 一、声音的三个要素: 1、音调:音频2、音量:声音震动的浮动3、音色:本质是谐波 人类听觉范围: 20HZ ...

  • 从0开始做播放器---音频播放有杂音且音调异常

    我的播放器,音频像是电视信号不好,需要动一下天线,有那种沙沙声。明确音频数据问题,在get音频数据处找问题。 音调...

  • 成功日记20180221

    ①完成健步8000+ ②音频录音调试 ③协助RZ做放松训练 ④重新体验系统脱敏法

网友评论

    本文标题:音频特征(5):改变音调

    本文链接:https://www.haomeiwen.com/subject/xakscctx.html