近期偶然间看到一个开源项目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("文件打开失败.\n"); 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 = ‘\0‘; } } else if (drv) *drv = ‘\0‘; 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 = ‘\0‘; } if (dir) { for (s = path; s < p;) *dir++ = *s++; *dir = ‘\0‘; } } int main(int argc, char* argv[]) { std::cout << "Audio Processing " << std::endl; std::cout << "博客:http://tntmonks.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 << "按任意键退出程序 \n" << std::endl; return 0; }
示例具体流程为:
加载mp3(拖放mp3文件到可执行文件上)->解码mp3->保存wav
并对 加载,保存 这2个环节都进行了耗时计算并输出。
若有其他相关问题或者需求也可以邮件联系俺探讨。
邮箱地址是:
gaozhihan@vip.qq.com
若此博文能帮到您,欢迎扫码小额赞助。
微信:
支付宝: