• mp3格式转wav格式 附完整C++算法实现代码


    近期偶然间看到一个开源项目minimp3

    Minimalistic MP3 decoder single header library

    项目地址:

    https://github.com/lieff/minimp3

    单文件头的最小mp3解码器。

    一直很想抽时间好好看上一看。

    最好的学习方式就是写个实用性的工程项目。

    例如实现mp3转wav格式。

    嗯,这篇博文就是这么来的。

    阅读了下minimp3的源码,有一两处小bug,

    这个解码算法可以进一步提速优化的地方还有不少。

    后面有时间,再好好庖丁解牛。

    基于这个库,实现mp3转wav的代码行数不到300行。

    小巧而简洁,算是简单的抛砖引玉了。

    个人习惯,很少写注释,

    所以尽可能把代码写得清晰易懂,当然也有犯懒的时候。

     完整代码:

    #define _CRT_SECURE_NO_WARNINGS
    #define _CRT_SECURE_NO_DEPRECATE 1 
    #define _CRT_NONSTDC_NO_DEPRECATE 1
    #include <stdio.h>
    #include <stdlib.h>    
    #include <stdint.h>    
    #include <time.h> 
    #include <iostream>  
    
    // ref:https://github.com/lieff/minimp3/blob/master/minimp3.h
    #define MINIMP3_IMPLEMENTATION
    #include "minimp3.h"
    #include <sys/stat.h>
    auto const epoch = clock();
    static double now()
    {
        return  (clock() - epoch);
    };
    
    template <typename FN>
    static double bench(const FN &fn)
    {
        auto took = -now();
        return (fn(), took + now()) / 1000;
    }
    
    //写wav文件
    void wavWrite_int16(char* filename, int16_t* buffer, int sampleRate, uint32_t totalSampleCount, int channels = 1) {
    
        FILE* fp = fopen(filename, "wb");
        if (fp == NULL) {
            printf("文件打开失败.
    ");
            return;
        }
        //修正写入的buffer长度
        totalSampleCount *= sizeof(int16_t)*channels;
        int nbit = 16;
        int FORMAT_PCM = 1;
        int nbyte = nbit / 8;
        char text[4] = { 'R', 'I', 'F', 'F' };
        uint32_t long_number = 36 + totalSampleCount;
        fwrite(text, 1, 4, fp);
        fwrite(&long_number, 4, 1, fp);
        text[0] = 'W';
        text[1] = 'A';
        text[2] = 'V';
        text[3] = 'E';
        fwrite(text, 1, 4, fp);
        text[0] = 'f';
        text[1] = 'm';
        text[2] = 't';
        text[3] = ' ';
        fwrite(text, 1, 4, fp);
    
        long_number = 16;
        fwrite(&long_number, 4, 1, fp);
        int16_t short_number = FORMAT_PCM;//默认音频格式
        fwrite(&short_number, 2, 1, fp);
        short_number = channels; // 音频通道数
        fwrite(&short_number, 2, 1, fp);
        long_number = sampleRate; // 采样率
        fwrite(&long_number, 4, 1, fp);
        long_number = sampleRate * nbyte; // 比特率
        fwrite(&long_number, 4, 1, fp);
        short_number = nbyte; // 块对齐
        fwrite(&short_number, 2, 1, fp);
        short_number = nbit; // 采样精度
        fwrite(&short_number, 2, 1, fp);
        char data[4] = { 'd', 'a', 't', 'a' };
        fwrite(data, 1, 4, fp);
        long_number = totalSampleCount;
        fwrite(&long_number, 4, 1, fp);
        fwrite(buffer, totalSampleCount, 1, fp);
        fclose(fp);
    }
    //读取文件buffer
    char *getFileBuffer(const char *fname, int *size)
    {
        FILE * fd = fopen(fname, "rb");
        if (fd == 0)
            return 0;
        struct stat st;
        char *file_buf = 0;
        if (fstat(fileno(fd), &st) < 0)
            goto doexit;
        file_buf = (char *)malloc(st.st_size + 1);
        if (file_buf != NULL)
        {
            if (fread(file_buf, st.st_size, 1, fd) < 1)
            {
                fclose(fd);
                return 0;
            }
            file_buf[st.st_size] = 0;
        }
    
        if (size)
            *size = st.st_size;
    doexit:
        fclose(fd);
        return file_buf;
    }
    //mp3解码
    int16_t* DecodeMp3ToBuffer(char* filename, uint32_t *sampleRate, uint32_t *totalSampleCount, unsigned int *channels)
    {
        int music_size = 0;
        int alloc_samples = 1024 * 1024, num_samples = 0;
        int16_t *music_buf = (int16_t *)malloc(alloc_samples * 2 * 2);
        unsigned char *file_buf = (unsigned char *)getFileBuffer(filename, &music_size);
        if (file_buf != NULL)
        {
            unsigned char *buf = file_buf;
            mp3dec_frame_info_t info;
            mp3dec_t dec;
    
            mp3dec_init(&dec);
            for (;;)
            {
                int16_t frame_buf[2 * 1152];
                int samples = mp3dec_decode_frame(&dec, buf, music_size, frame_buf, &info);
                if (alloc_samples < (num_samples + samples))
                {
                    alloc_samples *= 2;
                    int16_t* tmp = (int16_t *)realloc(music_buf, alloc_samples * 2 * info.channels);
                    if (tmp)
                        music_buf = tmp;
                }
                if (music_buf)
                    memcpy(music_buf + num_samples*info.channels, frame_buf, samples*info.channels * 2);
                num_samples += samples;
                if (info.frame_bytes <= 0 || music_size <= (info.frame_bytes + 4))
                    break;
                buf += info.frame_bytes;
                music_size -= info.frame_bytes;
            }
            if (alloc_samples > num_samples)
            {
                int16_t* tmp = (int16_t *)realloc(music_buf, num_samples * 2 * info.channels);
                if (tmp)
                    music_buf = tmp;
            }
    
            if (sampleRate)
                *sampleRate = info.hz;
            if (channels)
                *channels = info.channels;
            if (num_samples)
                *totalSampleCount = num_samples;
    
            free(file_buf);
            return music_buf;
        }
        if (music_buf)
            free(music_buf);
        return 0;
    }
    //分割路径函数
    void splitpath(const char* path, char* drv, char* dir, char* name, char* ext)
    {
        const char* end;
        const char* p;
        const char* s;
        if (path[0] && path[1] == ':') {
            if (drv) {
                *drv++ = *path++;
                *drv++ = *path++;
                *drv = '';
            }
        }
        else if (drv)
            *drv = '';
        for (end = path; *end && *end != ':';)
            end++;
        for (p = end; p > path && *--p != '\' && *p != '/';)
            if (*p == '.') {
                end = p;
                break;
            }
        if (ext)
            for (s = end; (*ext = *s++);)
                ext++;
        for (p = end; p > path;)
            if (*--p == '\' || *p == '/') {
                p++;
                break;
            }
        if (name) {
            for (s = p; s < end;)
                *name++ = *s++;
            *name = '';
        }
        if (dir) {
            for (s = path; s < p;)
                *dir++ = *s++;
            *dir = '';
        }
    }
    
    
    int main(int argc, char* argv[])
    {
        std::cout << "Audio Processing " << std::endl;
        std::cout << "博客:http://cpuimage.cnblogs.com/" << std::endl;
        std::cout << "mp3 转 wav." << std::endl;
    
        if (argc < 2) return -1;
        char* in_file = argv[1];
    
        //总音频采样数
        uint32_t totalSampleCount = 0;
        //音频采样率
        uint32_t sampleRate = 0;
        //通道数
        unsigned int channels = 0;
        int16_t* wavBuffer = NULL;
        double nLoadTime = bench([&]
        {
            wavBuffer = DecodeMp3ToBuffer(in_file, &sampleRate, &totalSampleCount, &channels);
        });
        std::cout << " 加载耗时: " << int(nLoadTime * 1000) << " 毫秒" << std::endl;
    
        //保存结果
        double nSaveTime = bench([&]
        {
            char drive[3];
            char dir[256];
            char fname[256];
            char ext[256];
            char out_file[1024];
            splitpath(in_file, drive, dir, fname, ext);
            sprintf(out_file, "%s%s%s.wav", drive, dir, fname);
            wavWrite_int16(out_file, wavBuffer, sampleRate, totalSampleCount, channels);
        });
        std::cout << " 保存耗时: " << int(nSaveTime * 1000) << " 毫秒" << std::endl;
        if (wavBuffer)
        {
            free(wavBuffer);
        }
        getchar();
        std::cout << "按任意键退出程序 
    " << std::endl;
        return 0;
    }

    示例具体流程为:

    加载mp3(拖放mp3文件到可执行文件上)->解码mp3->保存wav

    并对 加载,保存 这2个环节都进行了耗时计算并输出。

      

    若有其他相关问题或者需求也可以邮件联系俺探讨。

    邮箱地址是: 
    gaozhihan@vip.qq.com

    若此博文能帮到您,欢迎扫码小额赞助。

    微信:  

     

    支付宝: 

  • 相关阅读:
    “问答回复模块”Java开发文档官方改进版讲解【在线实习·吾研第二期】
    “付费邀请模块”产品原型图评审【在线实习·吾研第三期】
    “学长学姐认证模块”测试用例官方改进版讲解【在线实习·吾研第一期】
    “学长认证模块”Java代码2.0官方版要点讲解【在线实习·吾研第一期】
    “问答评论模块”UI作品评审【在线实习·吾研第二期】
    “认证模块”前端代码1.0评审【在线实习·吾研第一期】
    <<中国专利法详解>>学习笔记(一)
    JS 前端获得时间
    北漂的程序员
    Spring类注入异常
  • 原文地址:https://www.cnblogs.com/cpuimage/p/8282397.html
Copyright © 2020-2023  润新知