写代码,让你的声音变得高亢

小白:我是标准的男中音,唱“李香兰”不输张学友,为什么我要变成女高音?

本文介绍,如何修改音频数据,控制音频的节奏、速率或音调。

大概的思路是这样的,先解码音频,得到pcm数据,再通过soundtouch来修改pcm数据,最后压缩为常见格式的音频。

对于音频编码格式之类的知识,可以参考之前同系列的文章。

先给出一个经过修改后的音频文件,可以听一下效果(如果这里可以上传并播放音频文件的话):

解码与编码部分,同样是FFmpeg的使用(之前多次介绍过了),得到pcm后再调用soundtouch。

演示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);
}

// 保证这个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;
}

soundtouch,一个开源的音效处理项目(c++代码),可以用更改音频的音调、播放速率、节拍等特征。

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

soundtouch初始化:

soundtouch作用于pcm:

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

对于复杂的音效处理,必定会更耗时,对于在线实时的播放,音效的性能是要关注的因素,避免音效太耗时而导致播放卡顿。

小白:我听了处理后的“李香兰”,表示很满意,记得来听我的演唱会。

花满楼:那你要关注我们,并及时在群里面分布信息了!



写代码,让你的声音变得高亢

原文地址:http://blog.51cto.com/13136504/2060331

时间: 2024-10-24 10:44:20

写代码,让你的声音变得高亢的相关文章

关爱码农成长:关于写代码二三事

工作这么多年以来,一直从事软件相关领域,即使担任主管职务,也一直对技术充满热情.写代码写了这么多年,多少有些体会.我把自己对写代码这份工作的心得写下来,希望能给从事相关领域或有志于写代码的人参考. 一.你适合当程序员吗? 程序员,也叫软件工程师.程序设计师,我觉得「程序员」三个字简洁有力,是一种身份的象征. 如果你正从事这份工作,恭喜你!这是个热门行业,在可预见的将来,也不会消失.不过也别高兴太早,这一行的技术汰旧换新非常快,必须不断努力学习才行. 一点天赋 打开一个空白文档,必须创造出代码.与

养成良好的写代码习惯

看了上一节hello world一篇,相信你对C语言语法结构有了一定感触,其实基本上每个C语言程序代码都是那样的声明,那样的框架,而要填充的内容就是框架内的内容.会了hello world,其他的也就可以融会贯通,只不过是学习新的语句新的函数,新的算法的问题.所以在你写代码写的很6之前,我要讲一节习惯的问题.就是写代码的习惯. 为什么要养成好的写代码的习惯?因为你的代码并不只是要求能运行,还要美观易读.有可读性的代码才是有价值的代码,如果你写完代码,别人根本看不懂,甚至你自己都看不懂,那么这个代

苹果发布新一代编程语言Swift,边写代码边看结果,Apple Swift 简介

每年进入夏至前一个月,便是各高校毕业生的忙碌的季节-毕业论文,经非权威的调查显示,有近百分之九十的学生不到三十天就完成了论文的写作,更有百分之四十五的人不到十天就完成了毕业论文的写作,由此声音:这毕业论文在这么短的时间内完成质量高吗?对毕业后的工作有大的价值吗,工作单位会在乎毕业论文的质量吗? 因此得观点:毕业论文要不要写? 对于很多即将毕业的同学来说,毕业论文做的好与坏结果都是同样的毕业证,实在没有必要花太多的精力,通过万岁,一切只是为了顺利毕业."如果毕业论文不是跟学位证挂钩的话,我想以大多

MySQL 创始人:写代码比打游戏爽,程序员应多泡开源社区

根据StackOverflow的最新调查,MySQL仍然是全世界最流行的数据库,受访的开发者中有44.3%的人在使用,超过了第二位的SQL Server 10多个百分点.可是你知道MySQL是怎么诞生的吗?openocean 的一篇有关MySQL开发者Michael “Monty” Widenius的传记为我们揭秘. 大学计算机专业的数量总是会随着技术公司的兴衰而起伏,而现在,计算机科学正处在全盛时期.这意味着,不管怎样,计算机科学专业的毕业生绝大部分可能都会去敲2年的代码,再转到产品管理,然后

看外国女程序员如何直播写代码

我第一次直播写代码是在去年七月份.想要直播一下我在业余时间内为开源项目领域所做的工作,尽管在youtube上的大部分直播都是关于游戏的.我比较擅长于NodeJS的硬件库方面的工作(尽管大部分项目都是自己的).并且我在youtube上曾经开启过一个房间了,那为什么我不继续做下去?我的栏目可以叫做:基于JavaScript的硬件开发. 当然,我并不是第一个在直播平台直播代码的,Handmade Hero是我见到的第一个直播代码的人.接下来Vlambeer的工程师们也开始了直播代码,他们在youtub

不仅仅是写代码,而是完成作品

近来有人问起,现在似乎真得变成了码农,日出而作,日落而息.整天不停的写代码,开发业务需求,周而复始,日子长了,感到厌倦.有时回想,应该在过去的某个时期我也曾陷入过这样的循环中,后来又是如何脱离的呢? 代码与缘由 这要回归到从写代码这件事上开始.写代码是因为有需求,需求来自业务的发展需要,需求经过产品经理再传递到程序员. 刚开始,作为一个新手程序员,不停的为各种需求写代码.开发完一个,接着又是下一个,生生不息,循环不止.一开始也许会感觉有些累,但并没有产生太多的厌倦.这是一个从不熟悉到熟悉再到熟练

【转载】-如何写代码-编程智慧

原文地址:http://www.yinwang.org/blog-cn/2015/11/21/programming-philosophy 编程是一种创造性的工作,是一门艺术.精通任何一门艺术,都需要很多的练习和领悟,所以这里提出的"智慧",并不是号称一天瘦十斤的减肥药,它并不能代替你自己的勤奋.然而由于软件行业喜欢标新立异,喜欢把简单的事情搞复杂,我希望这些文字能给迷惑中的人们指出一些正确的方向,让他们少走一些弯路,基本做到一分耕耘一分收获. 反复推敲代码 既然"天才是百分

写代码要一以贯之

给每个抽象概念选一个代表词,并且一以贯之. 例如:代码中所有的获取某个变量值的函数都可以用getXXX()命名,用get来给在多个类中的同种方法命名. Eclipse,Android Studio,IntelliJ等变成环境,都提供了查看函数列表的功能.也就是说,如果你的代码编写保持着"一以贯之"的编写原则,那么当你想去获取一个变量值的时候,只需要输入"get",就可以所以出对应类下的所有以get开头的函数.这样就不用花费时间一个一个去找了. 也许你会说,代码是我写

转载-如何写代码-编程智慧

http://www.yinwang.org/blog-cn/2015/11/21/programming-philosophy/编程的智慧 编程是一种创造性的工作,是一门艺术.精通任何一门艺术,都需要很多的练习和领悟,所以这里提出的“智慧”,并不是号称一天瘦十斤的减肥药,它并不能代替你自己的勤奋.然而由于软件行业喜欢标新立异,喜欢把简单的事情搞复杂,我希望这些文字能给迷惑中的人们指出一些正确的方向,让他们少走一些弯路,基本做到一分耕耘一分收获. 反复推敲代码 既然“天才是百分之一的灵感,百分之