GSM6.10转码与wav文件保存
1、编码说明
gsm6.10编码:
gsm6.10是GSM通信网络的标准编码方式,才有8KHZ采样率,每20ms生成一个语音包,每个样本可以分为13位A率或者14位u率的16位PCM编码,每个采样生成的数据量为8000×20/1000×16=2560bits的原始数据,通过gsm编码器压缩为260bits的GSM帧,压缩后的数据率为1625byte,相当于13kbps,由于260位(32.5byte)的gsm帧不是8位的整数倍,因此编码器输出的GSM帧为264位的线性PCM码,即33个byte。
采样频率为8 kHz、每个样本为16位的未压缩的话音数据率为128 kbps,使用GSM压缩后的数据率为: (264bit×8000样本/s) / 160样本 = 13.2 kbps。GSM的压缩比:128:13.2 = 9.7,近似于10:1。
2、wav文件头部封装信息:
编码包括了两方面内容,一是按一定格式存储数据,二是采用一定的算法压缩数据。WAV格式对音频流的编码没有硬性规定,支持非压缩的PCM(Puls Code Modulation)脉冲编码调制格式,还支持压缩型的微软自适应分脉冲编码调制Microsoft ADPCM(Adaptive Differential Puls Code Modulation)、国际电报联盟(International Telegraph Union)制定的语音压缩标准ITUG.711 a-law、ITU G.711-law、IMA ADPCM、ITU G.723 ADPCM (Yamaha)、GSM 6.10、ITU G.721 ADPCM编码和其它压缩算法。MP3编码同样也可以运用在WAV中,只要安装相应的Decode,就可以播放WAV中的MP3音乐。
参考:参考WAV文件格式说明
3、编解码参考代码库:
gsm6.10编码每个为33byte,两个为66byte,在写入到wav中时需要将其中一个去掉一个byte,合并为65byte,同时需要设定GSM_OPT_WAV49格式,才能满足;
gsm6.10编码直接转换为rtp数据包时,字节截断的过程。
gsm编解码库
wav文件测试文件
4、gsm6.10转换为wav文件的参考代码:
gsm转为wav文件参考代码
/* makewave.c -- Read Sun .au and write GSM-containing MS wave file *//** Copyright (C) 1996 Jeffrey Chilton** Permission is granted to anyone to make or distribute copies of* this program, in any medium, provided that the copyright notice* and permission notice are preserved, and that the distributor* grants the recipient permission for further redistribution as* permitted by this notice.* * Author's E-mail address: jwc@chilton.com* */#include <stdio.h>#include "gsm.h"extern unsigned short MuLaw_Linear[];#define MULAW_ZERO 255int F_wav_fmt = 1;long read_header(FILE *);
int write_header(long, FILE *);#define BLOCK_SIZE 160main(argc, argv)
int argc;
char *argv[];
{register int i;FILE *sfp;FILE *dfp;gsm handle;long samples;long data_size;long total_out;unsigned char mulaw[2 * BLOCK_SIZE];gsm_signal linear[2 * BLOCK_SIZE];gsm_frame frame; int out_size;int rc;if (argc != 2){fprintf(stderr, "usage: %s <.au file>\n", argv[0]);exit(1);}sfp = fopen(argv[1], "r");if (!sfp){perror("open fails");exit(1);}/* Read .au file header info and calulate output size */samples = read_header(sfp);data_size = ((samples + (2 * BLOCK_SIZE - 1)) / (2 * BLOCK_SIZE));data_size *= 2 * sizeof (frame) - 1;/*
fprintf(stderr, "samples from header: 0x%x\n", samples);
fprintf(stderr, "calculated data size: 0x%x\n", data_size);
*/dfp = stdout;/* Create the GSM codec object and option it for wave framing */handle = gsm_create();if (!handle){perror("cannot create gsm codec");exit(1);}(void )gsm_option(handle, GSM_OPT_WAV49, &F_wav_fmt);/* Write the .wav file header */rc = write_header(data_size, dfp);if (rc){perror("error writing header");exit(1);}/* Compress the audio */total_out = 0;while (samples > 0){/* Read two frames worth of samples and convert to linear */rc = fread(mulaw, (size_t )1, sizeof (mulaw), sfp);if (rc < 0){perror("error reading input");exit(1);}samples -= rc;if (rc < sizeof (mulaw)){memset((char *)mulaw + rc, MULAW_ZERO, sizeof (mulaw) - rc);}for (i = 0; i < sizeof (mulaw); i++){linear[i] = MuLaw_Linear[mulaw[i]];}/* Encode the even half and write short (32-byte) frame */gsm_encode(handle, &linear[0], frame);out_size = sizeof (frame) - 1;rc = fwrite(frame, (size_t )1, out_size, dfp);if (rc != out_size){perror("error writing output");exit(1);}total_out += rc;/* Encode the odd half and write long (33-byte) frame */gsm_encode(handle, &linear[160], frame);out_size = sizeof (frame);rc = fwrite(frame, (size_t )1, out_size, dfp);if (rc != out_size){perror("error writing output");exit(1);}total_out += rc;}/* Pad output to even number of bytes */if (total_out & 0x1){frame[0] = 0x00;rc = fwrite(frame, (size_t )1, 1, dfp);if (rc != 1){perror("error writing output");exit(1);}total_out += rc;}/*
fprintf(stderr, "total bytes written: 0x%lx\n", total_out);
*//* Clean up */gsm_destroy(handle);}/* read_header - read Sun .au file header */static long
getlong(fp)
FILE *fp;
{long l;l = 0;l = (l << 8) | 0xFF & getc(fp);l = (l << 8) | 0xFF & getc(fp);l = (l << 8) | 0xFF & getc(fp);l = (l << 8) | 0xFF & getc(fp);return l;}#define AU_FILE_MAGIC 0x2e736e64
#define AU_FILE_MULAW_8 1
#define SAMPLE_RATE 8000
#define SAMPLE_CHANNELS 1long
read_header(fp)
FILE *fp;
{long magic;long header_size;long data_size;long code;long rate;long n_chan;int rc;magic = getlong(fp);if (magic != AU_FILE_MAGIC){fprintf(stderr, "input is not an audio file\n");rc = -1;goto out;}header_size = getlong(fp);data_size = getlong(fp);code = getlong(fp);rate = getlong(fp);n_chan = getlong(fp);header_size -= 24;while (header_size){(void )getc(fp);header_size--;}if (code != AU_FILE_MULAW_8 ||rate != SAMPLE_RATE ||n_chan != SAMPLE_CHANNELS){fprintf(stderr, "input must by mu-law 8KHz mono\n");rc = 01;goto out;}rc = data_size;out:return rc;}unsigned short MuLaw_Linear[] = {33280, 34308, 35336, 36364, 37393, 38421, 39449, 40477,41505, 42534, 43562, 44590, 45618, 46647, 47675, 48703,49474, 49988, 50503, 51017, 51531, 52045, 52559, 53073,53587, 54101, 54616, 55130, 55644, 56158, 56672, 57186,57572, 57829, 58086, 58343, 58600, 58857, 59114, 59371,59628, 59885, 60142, 60399, 60656, 60913, 61171, 61428,61620, 61749, 61877, 62006, 62134, 62263, 62392, 62520,62649, 62777, 62906, 63034, 63163, 63291, 63420, 63548,63645, 63709, 63773, 63838, 63902, 63966, 64030, 64095,64159, 64223, 64287, 64352, 64416, 64480, 64544, 64609,64657, 64689, 64721, 64753, 64785, 64818, 64850, 64882,64914, 64946, 64978, 65010, 65042, 65075, 65107, 65139,65163, 65179, 65195, 65211, 65227, 65243, 65259, 65275,65291, 65308, 65324, 65340, 65356, 65372, 65388, 65404,65416, 65424, 65432, 65440, 65448, 65456, 65464, 65472,65480, 65488, 65496, 65504, 65512, 65520, 65528, 0,32256, 31228, 30200, 29172, 28143, 27115, 26087, 25059,24031, 23002, 21974, 20946, 19918, 18889, 17861, 16833,16062, 15548, 15033, 14519, 14005, 13491, 12977, 12463,11949, 11435, 10920, 10406, 9892, 9378, 8864, 8350,7964, 7707, 7450, 7193, 6936, 6679, 6422, 6165, 5908, 5651, 5394, 5137, 4880, 4623, 4365, 4108, 3916, 3787, 3659, 3530, 3402, 3273, 3144, 3016, 2887, 2759, 2630, 2502, 2373, 2245, 2116, 1988, 1891, 1827, 1763, 1698, 1634, 1570, 1506, 1441, 1377, 1313, 1249, 1184, 1120, 1056, 992, 927, 879, 847, 815, 783, 751, 718, 686, 654,622, 590, 558, 526, 494, 461, 429, 397,373, 357, 341, 325, 309, 293, 277, 261,245, 228, 212, 196, 180, 164, 148, 132,120, 112, 104, 96, 88, 80, 72, 64,56, 48, 40, 32, 24, 16, 8, 0};/* write_header - write (approximation of) MS wave file header */static int
fputlong(x, fp)
long x;
FILE *fp;
{return fputc(x & 0xFF, fp) == EOF ||fputc((x >> 8) & 0xFF, fp) == EOF ||fputc((x >> 16) & 0xFF, fp) == EOF ||fputc((x >> 24) & 0xFF, fp) == EOF;
}static int
fputshort(x, fp)
short x;
FILE *fp;
{return fputc(x & 0xFF, fp) == EOF ||fputc((x >> 8) & 0xFF, fp) == EOF;
}#define WAVE_HS 20
#define FACT_HS 4#define GSM_FMT 49 /* Format code number */
#define N_CHAN 1 /* Number of channels (mono) */
#define SAMP_FREQ 8000 /* Uncompressed samples/second */
#define BYTE_FREQ 1625 /* Compressed bytes/second */
#define X_1 65 /* Unknown format-specific */
#define X_2 2 /* Unknown format-specific */
#define X_3 320 /* Unknown format-specific */#define Y_1 20160 /* Unknown "fact" value */int
write_header(data_size, fp)
long data_size;
FILE *fp;
{unsigned short s;int rc;rc = 0;rc |= fputs("RIFF", fp) == EOF;rc |= fputlong(52 + ((data_size + 1) & ~0x1), fp);rc |= fputs("WAVEfmt ", fp) == EOF;rc |= fputlong(WAVE_HS, fp);rc |= fputshort(GSM_FMT, fp);rc |= fputshort(N_CHAN, fp);rc |= fputlong(SAMP_FREQ, fp);rc |= fputlong(BYTE_FREQ, fp);rc |= fputlong(X_1, fp);rc |= fputshort(X_2, fp);rc |= fputshort(X_3, fp);rc |= fputs("fact", fp) == EOF;rc |= fputlong(FACT_HS, fp);rc |= fputlong(Y_1, fp);rc |= fputs("data", fp) == EOF;rc |= fputlong(data_size, fp);return rc;
}
gsm6.10的wav文件解码到pcm16参考代码
/*2 * Interface to libgsm for gsm encoding/decoding3 * Copyright (c) 2005 Alban Bedel <albeu@free.fr>4 * Copyright (c) 2006, 2007 Michel Bardiaux <mbardiaux@mediaxim.be>5 *6 * This file is part of FFmpeg.7 *8 * FFmpeg is free software; you can redistribute it and/or9 * modify it under the terms of the GNU Lesser General Public10 * License as published by the Free Software Foundation; either11 * version 2.1 of the License, or (at your option) any later version.12 *13 * FFmpeg is distributed in the hope that it will be useful,14 * but WITHOUT ANY WARRANTY; without even the implied warranty of15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU16 * Lesser General Public License for more details.17 *18 * You should have received a copy of the GNU Lesser General Public19 * License along with FFmpeg; if not, write to the Free Software20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA21 */22 23 /**24 * @file25 * Interface to libgsm for gsm encoding/decoding26 */27 28 // The idiosyncrasies of GSM-in-WAV are explained at http://kbs.cs.tu-berlin.de/~jutta/toast.html29 30 #include "config.h"31 #if HAVE_GSM_H32 #include <gsm.h>33 #else34 #include <gsm/gsm.h>35 #endif36 37 #include "libavutil/channel_layout.h"38 #include "libavutil/common.h"39 #include "avcodec.h"40 #include "internal.h"41 #include "gsm.h"42 43 static av_cold int libgsm_encode_close(AVCodecContext *avctx) {44 gsm_destroy(avctx->priv_data);45 avctx->priv_data = NULL;46 return 0;47 }48 49 static av_cold int libgsm_encode_init(AVCodecContext *avctx) {50 if (avctx->channels > 1) {51 av_log(avctx, AV_LOG_ERROR, "Mono required for GSM, got %d channels\n",52 avctx->channels);53 return -1;54 }55 56 if (avctx->sample_rate != 8000) {57 av_log(avctx, AV_LOG_ERROR, "Sample rate 8000Hz required for GSM, got %dHz\n",58 avctx->sample_rate);59 if (avctx->strict_std_compliance > FF_COMPLIANCE_UNOFFICIAL)60 return -1;61 }62 if (avctx->bit_rate != 13000 /* Official */ &&63 avctx->bit_rate != 13200 /* Very common */ &&64 avctx->bit_rate != 0 /* Unknown; a.o. mov does not set bitrate when decoding */ ) {65 av_log(avctx, AV_LOG_ERROR, "Bitrate 13000bps required for GSM, got %dbps\n",66 avctx->bit_rate);67 if (avctx->strict_std_compliance > FF_COMPLIANCE_UNOFFICIAL)68 return -1;69 }70 71 avctx->priv_data = gsm_create();72 if (!avctx->priv_data)73 goto error;74 75 switch(avctx->codec_id) {76 case AV_CODEC_ID_GSM:77 avctx->frame_size = GSM_FRAME_SIZE;78 avctx->block_align = GSM_BLOCK_SIZE;79 break;80 case AV_CODEC_ID_GSM_MS: {81 int one = 1;82 gsm_option(avctx->priv_data, GSM_OPT_WAV49, &one);83 avctx->frame_size = 2*GSM_FRAME_SIZE;84 avctx->block_align = GSM_MS_BLOCK_SIZE;85 }86 }87 88 return 0;89 error:90 libgsm_encode_close(avctx);91 return -1;92 }93 94 static int libgsm_encode_frame(AVCodecContext *avctx, AVPacket *avpkt,95 const AVFrame *frame, int *got_packet_ptr)96 {97 int ret;98 gsm_signal *samples = (gsm_signal *)frame->data[0];99 struct gsm_state *state = avctx->priv_data;100 101 if ((ret = ff_alloc_packet2(avctx, avpkt, avctx->block_align)) < 0)102 return ret;103 104 switch(avctx->codec_id) {105 case AV_CODEC_ID_GSM:106 gsm_encode(state, samples, avpkt->data);107 break;108 case AV_CODEC_ID_GSM_MS:109 gsm_encode(state, samples, avpkt->data);110 gsm_encode(state, samples + GSM_FRAME_SIZE, avpkt->data + 32);111 }112 113 *got_packet_ptr = 1;114 return 0;115 }116 117 118 #if CONFIG_LIBGSM_ENCODER119 AVCodec ff_libgsm_encoder = {120 .name = "libgsm",121 .long_name = NULL_IF_CONFIG_SMALL("libgsm GSM"),122 .type = AVMEDIA_TYPE_AUDIO,123 .id = AV_CODEC_ID_GSM,124 .init = libgsm_encode_init,125 .encode2 = libgsm_encode_frame,126 .close = libgsm_encode_close,127 .sample_fmts = (const enum AVSampleFormat[]){ AV_SAMPLE_FMT_S16,128 AV_SAMPLE_FMT_NONE },129 };130 #endif131 #if CONFIG_LIBGSM_MS_ENCODER132 AVCodec ff_libgsm_ms_encoder = {133 .name = "libgsm_ms",134 .long_name = NULL_IF_CONFIG_SMALL("libgsm GSM Microsoft variant"),135 .type = AVMEDIA_TYPE_AUDIO,136 .id = AV_CODEC_ID_GSM_MS,137 .init = libgsm_encode_init,138 .encode2 = libgsm_encode_frame,139 .close = libgsm_encode_close,140 .sample_fmts = (const enum AVSampleFormat[]){ AV_SAMPLE_FMT_S16,141 AV_SAMPLE_FMT_NONE },142 };143 #endif144 145 typedef struct LibGSMDecodeContext {146 struct gsm_state *state;147 } LibGSMDecodeContext;148 149 static av_cold int libgsm_decode_init(AVCodecContext *avctx) {150 LibGSMDecodeContext *s = avctx->priv_data;151 152 avctx->channels = 1;153 avctx->channel_layout = AV_CH_LAYOUT_MONO;154 if (!avctx->sample_rate)155 avctx->sample_rate = 8000;156 avctx->sample_fmt = AV_SAMPLE_FMT_S16;157 158 s->state = gsm_create();159 160 switch(avctx->codec_id) {161 case AV_CODEC_ID_GSM:162 avctx->frame_size = GSM_FRAME_SIZE;163 avctx->block_align = GSM_BLOCK_SIZE;164 break;165 case AV_CODEC_ID_GSM_MS: {166 int one = 1;167 gsm_option(s->state, GSM_OPT_WAV49, &one);168 avctx->frame_size = 2 * GSM_FRAME_SIZE;169 avctx->block_align = GSM_MS_BLOCK_SIZE;170 }171 }172 173 return 0;174 }175 176 static av_cold int libgsm_decode_close(AVCodecContext *avctx) {177 LibGSMDecodeContext *s = avctx->priv_data;178 179 gsm_destroy(s->state);180 s->state = NULL;181 return 0;182 }183 184 static int libgsm_decode_frame(AVCodecContext *avctx, void *data,185 int *got_frame_ptr, AVPacket *avpkt)186 {187 int i, ret;188 LibGSMDecodeContext *s = avctx->priv_data;189 AVFrame *frame = data;190 uint8_t *buf = avpkt->data;191 int buf_size = avpkt->size;192 int16_t *samples;193 194 if (buf_size < avctx->block_align) {195 av_log(avctx, AV_LOG_ERROR, "Packet is too small\n");196 return AVERROR_INVALIDDATA;197 }198 199 /* get output buffer */200 frame->nb_samples = avctx->frame_size;201 if ((ret = ff_get_buffer(avctx, frame, 0)) < 0)202 return ret;203 samples = (int16_t *)frame->data[0];204 205 for (i = 0; i < avctx->frame_size / GSM_FRAME_SIZE; i++) {206 if ((ret = gsm_decode(s->state, buf, samples)) < 0)207 return -1;208 buf += GSM_BLOCK_SIZE;209 samples += GSM_FRAME_SIZE;210 }211 212 *got_frame_ptr = 1;213 214 return avctx->block_align;215 }216 217 static void libgsm_flush(AVCodecContext *avctx) {218 LibGSMDecodeContext *s = avctx->priv_data;219 int one = 1;220 221 gsm_destroy(s->state);222 s->state = gsm_create();223 if (avctx->codec_id == AV_CODEC_ID_GSM_MS)224 gsm_option(s->state, GSM_OPT_WAV49, &one);225 }226 227 #if CONFIG_LIBGSM_DECODER228 AVCodec ff_libgsm_decoder = {229 .name = "libgsm",230 .long_name = NULL_IF_CONFIG_SMALL("libgsm GSM"),231 .type = AVMEDIA_TYPE_AUDIO,232 .id = AV_CODEC_ID_GSM,233 .priv_data_size = sizeof(LibGSMDecodeContext),234 .init = libgsm_decode_init,235 .close = libgsm_decode_close,236 .decode = libgsm_decode_frame,237 .flush = libgsm_flush,238 .capabilities = CODEC_CAP_DR1,239 };240 #endif241 #if CONFIG_LIBGSM_MS_DECODER242 AVCodec ff_libgsm_ms_decoder = {243 .name = "libgsm_ms",244 .long_name = NULL_IF_CONFIG_SMALL("libgsm GSM Microsoft variant"),245 .type = AVMEDIA_TYPE_AUDIO,246 .id = AV_CODEC_ID_GSM_MS,247 .priv_data_size = sizeof(LibGSMDecodeContext),248 .init = libgsm_decode_init,249 .close = libgsm_decode_close,250 .decode = libgsm_decode_frame,251 .flush = libgsm_flush,252 .capabilities = CODEC_CAP_DR1,253 };254 #endif
5、转码验证工具
Adobe Audition CS6
WaveCN 2.0.0.5
GSM6.10转码与wav文件保存相关推荐
- C#文字转语音,实时播放以及mp3,wav文件保存
源码下载地址:https://download.csdn.net/download/horseroll/10500847 无积分付费下载地址:https://download.csdn.net/dow ...
- 安卓Android开发:使用AudioRecord录音、将录音保存为wav文件、使用AudioTrack保存录音
一.使用AudioRrecord录音 1.1声明 首先需要声明一个AudioRecord类的实例.之所以需要事先声明,是因为在本例中,录音的启动和结束被封装在两个不同的方法里.而通常来讲," ...
- VC++初步实现保存数据为音频WAV文件
先行基本知识见此 https://blog.csdn.net/bcbobo21cn/article/details/109087252 win7, vc6:新建一个对话框工程:添加一个文本框:为文本框 ...
- 解决使用 AVAudioRecorder 录音保存 .WAV 文件遇到的问题
问题背景 App 实现录音保存音频文件,并实现本地语音识别匹配功能. 通过网络请求上传通过语音匹配的音频文件. 服务器接收到文件并进行语音识别,使用的是第三方微软语音识别,只支持 PCM 数据源的 W ...
- 用python实现语音的分割并保存为.wav文件
功能描述 因为在研究使用openSMILE提取特征时,需要对语音进行分割,我找了很多方法,都无法实现自己想要效果,语音自己实现了语音分割并保存的代码,我测试了一下,可以达到自己想要的结果,因此写一 ...
- Python音频处理:创建一个正弦波并保存为wav文件
Python音频处理:创建一个正弦波并保存为wav文件 0. 预备知识 0.1 数字信号基础 0.2 声学概念基础 1. 创建一个正弦波 2. 保存为wav文件 0. 预备知识 0.1 数字信号基础 ...
- ffmpeg提取mp4文件中的音频,保存为wav文件
如题,一个命令行即可: ffmpeg -i 123.mp4 -acodec pcm_s16le -f s16le -ac 1 -ar 16000 -f wav 123.wav 这样就生成了一个wav文 ...
- 【数据压缩】WAV文件和AVI文件格式分析
一.WAV文件和AVI文件格式简介 WAV的英文全称是Waveform Audio File Format ,它采用 RIFF (Resource Interchange File Format)文件 ...
- AVI文件格式简介与WAV文件分析
AVI文件格式简介与WAV文件分析 AVI文件格式(回答问题) 所有AVI文件至少包含2个必须的LIST Chunk和一个索引Chunk Chunk: LIST Chunk: 小端:数据的低位保存在内 ...
最新文章
- dll的概念、dll导出类(转)
- 厦门计算机高级职称,2021年厦门工程师职称属于哪种职称?
- java操作mongodb_Java操作MongoDB
- java短横线转驼峰_Java后端常备的开发规范
- ipad连接电脑_这些应用让iPad生产力分分钟UP
- 陌陌估值1亿美元:一个用户10美元,贵吗?
- 面试官:怎么改进哈希算法实现负载均衡的扩展性和容错性?我:...
- CTS(10)---谷歌CTS测试之Verify简介
- c#类属性和实例属性_C#中类的序列化及反序列化简要分析
- mac 图形化安装mysql,mac安装mysql图形化工具?
- mpls 保留标签值_浅析MPLS多协议标签交换的发展历程
- 页脚保持在未满屏页面的底部
- 【渝粤教育】国家开放大学2018年春季 0100-22T程序设计基础 参考试题
- 22.哈希表(HashTable)
- 项目开发-文档-软件需求规格说明书模板文档命名规则及格式要求(免费下载链接)
- lightblue使用教程_使用LightBlue Bean和IFTTT自动化LIFX灯
- laravel 下载文件
- beanshell断言_jmeter BeanShell断言(一)
- 【干货】Excel中的换行符,这几种用法你会哪些?
- 西安交大计算机考研分数线2020院线,西安交大考研分数线2020院线_全国硕士研究生招生考试网...
热门文章
- 四种色彩模式ARGB_8888、ARGB_4444、 RGB_565、 ALPHA_8的区别
- 不经一番彻骨寒,怎得梅花扑鼻香
- caffe-ristretto:定点举例
- wangeditor富文本编辑器使用过程中遇到的问题以及解决办法
- redis数据类型介绍
- Xcode The 'Apple Push Notification' feature is only available to users enrolled in Apple Develo.
- 腾讯云服务器系统盘空间不足问题
- React学习二(组件详解)
- linux 下动态链接库的创建与使用——dlopen,dlsym
- 什么是微信商城?如何微商城?