日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 综合教程 >内容正文

综合教程

详解如何使用代码进行音频合成

發布時間:2023/12/31 综合教程 61 生活家
生活随笔 收集整理的這篇文章主要介紹了 详解如何使用代码进行音频合成 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

作者:鄭童宇

GitHub:https://github.com/CrazyZty

1.前言

  音頻合成在現實生活中應用廣泛,在網上可以搜索到不少相關的講解和代碼實現,但個人感覺在網上搜索到的音頻合成相關文章的講解都并非十分透徹,故而寫下本篇博文,計劃通過講解如何使用代碼實現音頻合成功能從而將本人對音頻合成的理解闡述給各位,力圖讀完的各位可以對音頻合成整體過程有一個清晰的了解。

  本篇博文以Java為示例語言,以Android為示例平臺。

  本篇博文著力于講解音頻合成實現原理與過程中的細節和潛在問題,目的是讓各位不被編碼語言所限制,在本質上理解如何實現音頻合成的功能。

2.音頻合成

2.1.功能簡介

  本次實現的音頻合成功能參考"唱吧"的音頻合成,功能流程是:錄音生成PCM文件,接著根據錄音時長對背景音樂文件進行解碼加裁剪,同時將解碼后的音頻調制到與錄音文件相同的采樣率,采樣點字節數,聲道數,接著根據指定系數對兩個音頻文件進行音量調節并合成為PCM文件,最后進行壓縮編碼生成MP3文件。

2.2.功能實現

2.2.1.錄音

  錄音功能生成的目標音頻格式是PCM格式,對于PCM的定義,維基百科上是這么寫到的:"Pulse-code modulation(PCM) is a method used todigitallyrepresent sampledanalog signals. It is the standard form ofdigital audioin computers,Compact Discs,digital telephonyand other digital audio applications. In a PCM stream, theamplitudeof the analog signal is sampled regularly at uniform intervals, and each sample isquantizedto the nearest value within a range of digital steps.",大致意思是PCM是用來采樣模擬信號的一種方法,是現在數字音頻應用中數字音頻的標準格式,而PCM采樣的原理,是均勻間隔的將模擬信號的振幅量化成指定數據范圍內最貼近的數值。

  PCM文件存儲的數據是不經壓縮的純音頻數據,當然只是這么說可能有些抽象,我們拉上大家熟知的MP3文件進行對比,MP3文件存儲的是壓縮后的音頻,PCM與MP3兩者之間的關系簡單說就是:PCM文件經過MP3壓縮算法處理后生成的文件就是MP3文件。我們簡單比較一下雙方存儲所消耗的空間,1分鐘的每采樣點16位的雙聲道的44.1kHz采樣率PCM文件大小為:1*60*16/8*2*44.1*1000/1024=10335.9375KB,約為10MB,而對應的128kps的MP3文件大小僅為1MB左右,既然PCM文件占用存儲空間這么大,我們是不是應該放棄使用PCM格式存儲錄音,恰恰相反,注意第一句話:"PCM文件存儲的數據是不經壓縮的純音頻數據",這意味只有PCM格式的音頻數據是可以用來直接進行聲音處理,例如進行音量調節,聲音濾鏡等操作,相對的其他的音頻編碼格式都是必須解碼后才能進行處理(PCM編碼的WAV文件也得先讀取文件頭),當然這不代表PCM文件就好用,因為沒有文件頭,所以進行處理或者播放之前我們必須事先知道PCM文件的聲道數,采樣點字節數,采樣率,編碼大小端,這在大多數情況下都是不可能的,事實上就我所知沒有播放器是直接支持PCM文件的播放。不過現在錄音的各項系數都是我們定義的,所以我們就不用擔心這個問題。

  背景知識了解這些就足夠了,下面我給出實現代碼,綜合代碼講解實現過程。

 1     if (recordVoice) {
 2         audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC,
 3                 Constant.RecordSampleRate, AudioFormat.CHANNEL_IN_MONO,
 4                 pcmFormat.getAudioFormat(), audioRecordBufferSize);
 5 
 6         try {
 7             audioRecord.startRecording();
 8         } catch (Exception e) {
 9             NoRecordPermission();
10             continue;
11         }
12 
13         BufferedOutputStream bufferedOutputStream = FileFunction
14                 .GetBufferedOutputStreamFromFile(recordFileUrl);
15 
16         while (recordVoice) {
17             int audioRecordReadDataSize =
18                     audioRecord.read(audioRecordBuffer, 0, audioRecordBufferSize);
19 
20             if (audioRecordReadDataSize > 0) {
21                 calculateRealVolume(audioRecordBuffer, audioRecordReadDataSize);
22                 if (bufferedOutputStream != null) {
23                     try {
24                         byte[] outputByteArray = CommonFunction
25                                 .GetByteBuffer(audioRecordBuffer,
26                                         audioRecordReadDataSize, Variable.isBigEnding);
27                         bufferedOutputStream.write(outputByteArray);
28                     } catch (IOException e) {
29                         e.printStackTrace();
30                     }
31                 }
32             } else {
33                 NoRecordPermission();
34                 continue;
35             }
36         }
37 
38         if (bufferedOutputStream != null) {
39             try {
40                 bufferedOutputStream.close();
41             } catch (Exception e) {
42                 LogFunction.error("關閉錄音輸出數據流異常", e);
43             }
44         }
45 
46         audioRecord.stop();
47         audioRecord.release();
48         audioRecord = null;
49     }

  錄音的實際實現和控制代碼較多,在此僅抽出核心的錄音代碼進行講解。在此為獲取錄音的原始數據,我使用了Android原生的AudioRecord,其他的平臺基本也會提供類似的工具類。這段代碼實現的功能是當錄音開始后,應用會根據設定的采樣率和聲道數以及采樣字節數來不斷從MIC中獲取原始的音頻數據,然后將獲取的音頻數據寫入到指定文件中,直至錄音結束。這段代碼邏輯比較清晰的,我就不過多講解了。

  潛在問題的話,手機平臺上是需要申請錄音權限的,如果沒有錄音權限就無法生成正確的錄音文件。

2.2.2.解碼與裁剪背景音樂

  如前文所說,除了PCM格式以外的所有音頻編碼格式的音頻都必須解碼后才可以處理,因此要讓背景音樂參與合成必須事先對背景音樂進行解碼,同時為減少合成的MP3文件的大小,需要根據錄音時長對解碼的音頻文件進行裁剪。本節不會詳細解釋解碼算法,因為每個平臺都會有對應封裝的工具類,直接使用即可。

  背景知識先講這些,本次功能實現過程中的潛在問題較多,下面我給出實現代碼,綜合代碼講解實現過程。

 1     private boolean decodeMusicFile(String musicFileUrl, String decodeFileUrl, int startSecond, int endSecond,
 2                                     Handler handler,
 3                                     DecodeOperateInterface decodeOperateInterface) {
 4         int sampleRate = 0;
 5         int channelCount = 0;
 6 
 7         long duration = 0;
 8 
 9         String mime = null;
10 
11         MediaExtractor mediaExtractor = new MediaExtractor();
12         MediaFormat mediaFormat = null;
13         MediaCodec mediaCodec = null;
14 
15         try {
16             mediaExtractor.setDataSource(musicFileUrl);
17         } catch (Exception e) {
18             LogFunction.error("設置解碼音頻文件路徑錯誤", e);
19             return false;
20         }
21 
22         mediaFormat = mediaExtractor.getTrackFormat(0);
23         sampleRate = mediaFormat.containsKey(MediaFormat.KEY_SAMPLE_RATE) ?
24                 mediaFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE) : 44100;
25         channelCount = mediaFormat.containsKey(MediaFormat.KEY_CHANNEL_COUNT) ?
26                 mediaFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT) : 1;
27         duration = mediaFormat.containsKey(MediaFormat.KEY_DURATION) ? mediaFormat.getLong(MediaFormat.KEY_DURATION) : 0;
28         mime = mediaFormat.containsKey(MediaFormat.KEY_MIME) ? mediaFormat.getString(MediaFormat.KEY_MIME) : "";
29 
30         LogFunction.log("歌曲信息",
31                 "Track info: mime:" + mime + " 采樣率sampleRate:" + sampleRate + " channels:" +
32                         channelCount + " duration:" + duration);
33 
34         if (CommonFunction.isEmpty(mime) || !mime.startsWith("audio/")) {
35             LogFunction.error("解碼文件不是音頻文件", "mime:" + mime);
36             return false;
37         }
38 
39         if (mime.equals("audio/ffmpeg")) {
40             mime = "audio/mpeg";
41             mediaFormat.setString(MediaFormat.KEY_MIME, mime);
42         }
43 
44         try {
45             mediaCodec = MediaCodec.createDecoderByType(mime);
46 
47             mediaCodec.configure(mediaFormat, null, null, 0);
48         } catch (Exception e) {
49             LogFunction.error("解碼器configure出錯", e);
50             return false;
51         }
52 
53         getDecodeData(mediaExtractor, mediaCodec, decodeFileUrl, sampleRate, channelCount, startSecond,
54                 endSecond, handler, decodeOperateInterface);
55         return true;
56     }

  decodeMusicFile方法的代碼主要功能是獲取背景音樂信息,初始化解碼器,最后調用getDecodeData方法正式開始對背景音樂進行處理。

  代碼中使用了Android原生工具類作為解碼器,事實上作為原生的解碼器,我也遇到過兼容性問題不得不做了一些相應的處理,不得不抱怨一句不同的Android定制系統實在是導致了太多的兼容性問題。

  1     private void getDecodeData(MediaExtractor mediaExtractor, MediaCodec mediaCodec, String decodeFileUrl, int sampleRate,
  2                                int channelCount, int startSecond, int endSecond,
  3                                Handler handler,
  4                                final DecodeOperateInterface decodeOperateInterface) {
  5         boolean decodeInputEnd = false;
  6         boolean decodeOutputEnd = false;
  7 
  8         int sampleDataSize;
  9         int inputBufferIndex;
 10         int outputBufferIndex;
 11         int byteNumber;
 12 
 13         long decodeNoticeTime = System.currentTimeMillis();
 14         long decodeTime;
 15         long presentationTimeUs = 0;
 16 
 17         final long timeOutUs = 100;
 18         final long startMicroseconds = startSecond * 1000 * 1000;
 19         final long endMicroseconds = endSecond * 1000 * 1000;
 20 
 21         ByteBuffer[] inputBuffers;
 22         ByteBuffer[] outputBuffers;
 23 
 24         ByteBuffer sourceBuffer;
 25         ByteBuffer targetBuffer;
 26 
 27         MediaFormat outputFormat = mediaCodec.getOutputFormat();
 28 
 29         MediaCodec.BufferInfo bufferInfo;
 30 
 31         byteNumber =
 32                 (outputFormat.containsKey("bit-width") ? outputFormat.getInteger("bit-width") : 0) / 8;
 33 
 34         mediaCodec.start();
 35 
 36         inputBuffers = mediaCodec.getInputBuffers();
 37         outputBuffers = mediaCodec.getOutputBuffers();
 38 
 39         mediaExtractor.selectTrack(0);
 40 
 41         bufferInfo = new MediaCodec.BufferInfo();
 42 
 43         BufferedOutputStream bufferedOutputStream = FileFunction
 44                 .GetBufferedOutputStreamFromFile(decodeFileUrl);
 45 
 46         while (!decodeOutputEnd) {
 47             if (decodeInputEnd) {
 48                 return;
 49             }
 50 
 51             decodeTime = System.currentTimeMillis();
 52 
 53             if (decodeTime - decodeNoticeTime > Constant.OneSecond) {
 54                 final int decodeProgress =
 55                         (int) ((presentationTimeUs - startMicroseconds) * Constant.NormalMaxProgress /
 56                                 endMicroseconds);
 57 
 58                 if (decodeProgress > 0) {
 59                     handler.post(new Runnable() {
 60                         @Override
 61                         public void run() {
 62                             decodeOperateInterface.updateDecodeProgress(decodeProgress);
 63                         }
 64                     });
 65                 }
 66 
 67                 decodeNoticeTime = decodeTime;
 68             }
 69 
 70             try {
 71                 inputBufferIndex = mediaCodec.dequeueInputBuffer(timeOutUs);
 72 
 73                 if (inputBufferIndex >= 0) {
 74                     sourceBuffer = inputBuffers[inputBufferIndex];
 75 
 76                     sampleDataSize = mediaExtractor.readSampleData(sourceBuffer, 0);
 77 
 78                     if (sampleDataSize < 0) {
 79                         decodeInputEnd = true;
 80                         sampleDataSize = 0;
 81                     } else {
 82                         presentationTimeUs = mediaExtractor.getSampleTime();
 83                     }
 84 
 85                     mediaCodec.queueInputBuffer(inputBufferIndex, 0, sampleDataSize,
 86                             presentationTimeUs,
 87                             decodeInputEnd ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0);
 88 
 89                     if (!decodeInputEnd) {
 90                         mediaExtractor.advance();
 91                     }
 92                 } else {
 93                     LogFunction.error("inputBufferIndex", "" + inputBufferIndex);
 94                 }
 95 
 96                 // decode to PCM and push it to the AudioTrack player
 97                 outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, timeOutUs);
 98 
 99                 if (outputBufferIndex < 0) {
100                     switch (outputBufferIndex) {
101                         case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED:
102                             outputBuffers = mediaCodec.getOutputBuffers();
103                             LogFunction.error("MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED",
104                                     "[AudioDecoder]output buffers have changed.");
105                             break;
106                         case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED:
107                             outputFormat = mediaCodec.getOutputFormat();
108 
109                             sampleRate = outputFormat.containsKey(MediaFormat.KEY_SAMPLE_RATE) ?
110                                     outputFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE) : sampleRate;
111                             channelCount = outputFormat.containsKey(MediaFormat.KEY_CHANNEL_COUNT) ?
112                                     outputFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT) : channelCount;
113                             byteNumber = (outputFormat.containsKey("bit-width") ? outputFormat.getInteger("bit-width") : 0) / 8;
114 
115                             LogFunction.error("MediaCodec.INFO_OUTPUT_FORMAT_CHANGED",
116                                     "[AudioDecoder]output format has changed to " +
117                                             mediaCodec.getOutputFormat());
118                             break;
119                         default:
120                             LogFunction.error("error",
121                                     "[AudioDecoder] dequeueOutputBuffer returned " +
122                                             outputBufferIndex);
123                             break;
124                     }
125                     continue;
126                 }
127 
128                 targetBuffer = outputBuffers[outputBufferIndex];
129 
130                 byte[] sourceByteArray = new byte[bufferInfo.size];
131 
132                 targetBuffer.get(sourceByteArray);
133                 targetBuffer.clear();
134 
135                 mediaCodec.releaseOutputBuffer(outputBufferIndex, false);
136 
137                 if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
138                     decodeOutputEnd = true;
139                 }
140 
141                 if (sourceByteArray.length > 0 && bufferedOutputStream != null) {
142                     if (presentationTimeUs < startMicroseconds) {
143                         continue;
144                     }
145 
146                     byte[] convertByteNumberByteArray = ConvertByteNumber(byteNumber, Constant.RecordByteNumber, sourceByteArray);
147 
148                     byte[] resultByteArray =
149                             ConvertChannelNumber(channelCount, Constant.RecordChannelNumber, Constant.RecordByteNumber,
150                                     convertByteNumberByteArray);
151 
152                     try {
153                         bufferedOutputStream.write(resultByteArray);
154                     } catch (Exception e) {
155                         LogFunction.error("輸出解壓音頻數據異常", e);
156                     }
157                 }
158 
159                 if (presentationTimeUs > endMicroseconds) {
160                     break;
161                 }
162             } catch (Exception e) {
163                 LogFunction.error("getDecodeData異常", e);
164             }
165         }
166 
167         if (bufferedOutputStream != null) {
168             try {
169                 bufferedOutputStream.close();
170             } catch (IOException e) {
171                 LogFunction.error("關閉bufferedOutputStream異常", e);
172             }
173         }
174 
175         if (sampleRate != Constant.RecordSampleRate) {
176             Resample(sampleRate, decodeFileUrl);
177         }
178 
179         if (mediaCodec != null) {
180             mediaCodec.stop();
181             mediaCodec.release();
182         }
183 
184         if (mediaExtractor != null) {
185             mediaExtractor.release();
186         }
187     }

  getDecodeData方法是此次的進行解碼和裁剪的核心,方法的傳入參數中mediaExtractor,mediaCodec用以實際控制處理背景音樂的音頻數據,decodeFileUrl用以指明解碼和裁剪后的PCM文件的存儲地址,sampleRate,channelCount分別用以指明背景音樂的采樣率,聲道數,startSecond用以指明裁剪背景音樂的開始時間,目前功能中默認為0,endSecond用以指明裁剪背景音樂的結束時間,數值大小由錄音時長直接決定。

  getDecodeData方法中通過不斷通過mediaCodec讀入背景音樂原始數據進行處理,然后解碼輸出到buffer從而獲取解碼后的數據,因為mediaCodec的讀取解碼方法和平臺相關就不過多描述,在解碼過程中通過startSecond與endSecond來控制解碼后音頻數據輸出的開始與結束。

  解碼和裁剪根據上文的描述是比較簡單的,通過平臺提供的工具類解碼背景音樂數據,然后通過變量裁剪出指定長度的解碼后音頻數據輸出到外文件,這一個流程結束功能就實現了,但在過程中存在幾個潛在問題點。

  首先,要進行合成處理的話,我們必須要保證錄音文件和解碼后文件的采樣率,采樣點字節數,以及聲道數相同,因為錄音文件的這三項系數已經固定,所以我們必須對解碼的音頻數據進行處理以保證最終生成的解碼文件三項系數和錄音文件一致。在http://blog.csdn.net/ownwell/article/details/8114121/,我們可以了解PCM文件常見的四種存儲格式。

  格式 字節1 字節2 字節3 字節4

  8位單聲道 0聲道 0聲道 0聲道 0聲道
  8位雙聲道 0聲道(左) 1聲道(右) 0聲道(左) 1聲道(右)
  16位單聲道 0聲道(低) 0聲道(高) 0聲道(低) 0聲道(高)
  16位雙聲道 0聲道(左,低字節) 0聲道(左,高字節) 1聲道(右,低字節) 1聲道(右,高字節)

  了解這些知識后,我們就可以知道如何編碼以將已知格式的音頻數據轉化到另一采樣點字節數和聲道數。

  getDecodeData方法中146行調用的ConvertByteNumber方法是通過處理音頻數據以保證解碼后音頻文件和錄音文件采樣點字節數相同。

 1     private static byte[] ConvertByteNumber(int sourceByteNumber, int outputByteNumber, byte[] sourceByteArray) {
 2         if (sourceByteNumber == outputByteNumber) {
 3             return sourceByteArray;
 4         }
 5 
 6         int sourceByteArrayLength = sourceByteArray.length;
 7 
 8         byte[] byteArray;
 9 
10         switch (sourceByteNumber) {
11             case 1:
12                 switch (outputByteNumber) {
13                     case 2:
14                         byteArray = new byte[sourceByteArrayLength * 2];
15 
16                         byte resultByte[];
17 
18                         for (int index = 0; index < sourceByteArrayLength; index += 1) {
19                             resultByte = CommonFunction.GetBytes((short) (sourceByteArray[index] * 256), Variable.isBigEnding);
20 
21                             byteArray[2 * index] = resultByte[0];
22                             byteArray[2 * index + 1] = resultByte[1];
23                         }
24 
25                         return byteArray;
26                 }
27                 break;
28             case 2:
29                 switch (outputByteNumber) {
30                     case 1:
31                         int outputByteArrayLength = sourceByteArrayLength / 2;
32 
33                         byteArray = new byte[outputByteArrayLength];
34 
35                         for (int index = 0; index < outputByteArrayLength; index += 1) {
36                             byteArray[index] = (byte) (CommonFunction.GetShort(sourceByteArray[2 * index],
37                                     sourceByteArray[2 * index + 1], Variable.isBigEnding) / 256);
38                         }
39 
40                         return byteArray;
41                 }
42                 break;
43         }
44 
45         return sourceByteArray;
46     }

  ConvertByteNumber方法的參數中sourceByteNumber代表背景音樂文件采樣點字節數,outputByteNumber代表錄音文件采樣點字節數,兩者如果相同就不處理,不相同則根據背景音樂文件采樣點字節數進行不同的處理,本方法只對單字節存儲和雙字節存儲進行了處理,歡迎在各位Github上填充其他采樣點字節數的處理方法,

  getDecodeData方法中149行調用的ConvertChannelNumber方法是通過處理音頻數據以保證解碼后音頻文件和錄音文件聲道數相同。

 1     private static byte[] ConvertChannelNumber(int sourceChannelCount, int outputChannelCount, int byteNumber,
 2                                                byte[] sourceByteArray) {
 3         if (sourceChannelCount == outputChannelCount) {
 4             return sourceByteArray;
 5         }
 6 
 7         switch (byteNumber) {
 8             case 1:
 9             case 2:
10                 break;
11             default:
12                 return sourceByteArray;
13         }
14 
15         int sourceByteArrayLength = sourceByteArray.length;
16 
17         byte[] byteArray;
18 
19         switch (sourceChannelCount) {
20             case 1:
21                 switch (outputChannelCount) {
22                     case 2:
23                         byteArray = new byte[sourceByteArrayLength * 2];
24 
25                         byte firstByte;
26                         byte secondByte;
27 
28                         switch (byteNumber) {
29                             case 1:
30                                 for (int index = 0; index < sourceByteArrayLength; index += 1) {
31                                     firstByte = sourceByteArray[index];
32 
33                                     byteArray[2 * index] = firstByte;
34                                     byteArray[2 * index + 1] = firstByte;
35                                 }
36                                 break;
37                             case 2:
38                                 for (int index = 0; index < sourceByteArrayLength; index += 2) {
39                                     firstByte = sourceByteArray[index];
40                                     secondByte = sourceByteArray[index + 1];
41 
42                                     byteArray[2 * index] = firstByte;
43                                     byteArray[2 * index + 1] = secondByte;
44                                     byteArray[2 * index + 2] = firstByte;
45                                     byteArray[2 * index + 3] = secondByte;
46                                 }
47                                 break;
48                         }
49 
50                         return byteArray;
51                 }
52                 break;
53             case 2:
54                 switch (outputChannelCount) {
55                     case 1:
56                         int outputByteArrayLength = sourceByteArrayLength / 2;
57 
58                         byteArray = new byte[outputByteArrayLength];
59 
60                         switch (byteNumber) {
61                             case 1:
62                                 for (int index = 0; index < outputByteArrayLength; index += 2) {
63                                     short averageNumber =
64                                             (short) ((short) sourceByteArray[2 * index] + (short) sourceByteArray[2 * index + 1]);
65                                     byteArray[index] = (byte) (averageNumber >> 1);
66                                 }
67                                 break;
68                             case 2:
69                                 for (int index = 0; index < outputByteArrayLength; index += 2) {
70                                     byte resultByte[] = CommonFunction.AverageShortByteArray(sourceByteArray[2 * index],
71                                             sourceByteArray[2 * index + 1], sourceByteArray[2 * index + 2],
72                                             sourceByteArray[2 * index + 3], Variable.isBigEnding);
73 
74                                     byteArray[index] = resultByte[0];
75                                     byteArray[index + 1] = resultByte[1];
76                                 }
77                                 break;
78                         }
79 
80                         return byteArray;
81                 }
82                 break;
83         }
84 
85         return sourceByteArray;
86     }

  ConvertChannelNumber方法的參數中sourceChannelCount代表背景音樂文件聲道數,outputChannelCount代表錄音文件聲道數,兩者如果相同就不處理,不相同則根據聲道數和采樣點字節數進行不同的處理,本方法只對單雙通道進行了處理,歡迎在Github上填充立體聲等聲道的處理方法。

  getDecodeData方法中176行調用的Resample方法是用以處理音頻數據以保證解碼后音頻文件和錄音文件采樣率相同。

 1     private static void Resample(int sampleRate, String decodeFileUrl) {
 2         String newDecodeFileUrl = decodeFileUrl + "new";
 3 
 4         try {
 5             FileInputStream fileInputStream =
 6                     new FileInputStream(new File(decodeFileUrl));
 7             FileOutputStream fileOutputStream =
 8                     new FileOutputStream(new File(newDecodeFileUrl));
 9 
10             new SSRC(fileInputStream, fileOutputStream, sampleRate, Constant.RecordSampleRate,
11                     Constant.RecordByteNumber, Constant.RecordByteNumber, 1, Integer.MAX_VALUE, 0, 0, true);
12 
13             fileInputStream.close();
14             fileOutputStream.close();
15 
16             FileFunction.RenameFile(newDecodeFileUrl, decodeFileUrl);
17         } catch (IOException e) {
18             LogFunction.error("關閉bufferedOutputStream異常", e);
19         }
20     }

  為了修改采樣率,在此使用了SSRC在Java端的實現,在網上可以搜到一份關于SSRC的介紹:"SSRC = Synchronous Sample Rate Converter,同步采樣率轉換,直白地說就是只能做整數倍頻,不支持任意頻率之間的轉換,比如44.1KHz<->48KHz。",但不同的SSRC實現原理有所不同,我是用的是來自https://github.com/shibatch/SSRC在Java端的實現,簡單讀了此SSRC在Java端實現的源碼,其代碼實現中通過判別重采樣前后采樣率的最大公約數是否滿足設定條件作為是否可重采樣的依據,可以支持常見的非整數倍頻率的采樣率轉化,如44.1khz<->48khz,但如果目標采樣率是比較特殊的采樣率如某一較大的質數,那就無法支持重采樣。

  至此,Resample,ConvertByteNumber,ConvertChannelNumber三個方法的處理保證了解碼后文件和錄音文件的采樣率,采樣點字節數,以及聲道數相同。

  接著,此處潛在的第二個問題就是大小端存儲。 對計算機體系結構有所了解的同學肯定了解"大小端"這個概念,大小端分別代表了多字節數據在內存中組織的兩種不同順序,如果對于"大小端"不是太了解,可以瀏覽http://blog.jobbole.com/102432/的闡述,在處理音頻數據的方法中,我們可以看到"Variable.isBigEnding"這個參數,這個參數的含義就是當前平臺是否使用大端編碼,這里大家肯定會有疑問,內存中多字節數據的組織順序為什么會影響我們對音頻數據的處理,舉個例子,如果我們在將采樣點8位的音頻數據轉化為采樣點16位,目前的做法是將原始數據乘以256,相當于每一個byte轉化為short,同時short的高字節為原byte的內容,低字節為0,那現在問題來了,那就是高字節放到高地址還是低地址,這就和平臺采用的大小端存儲格式息息相關了,當然如果我們輸出的數據類型是short那就不用關心,Java會幫我們處理掉,但我們輸出的是byte數組,這就需要我們自己對數據進行處理了。

  這是一個很容易忽視的問題,因為正常情況下的軟件開發過程中我們基本是不用關心大小端的問題的,但在這里必須對大小端的情況進行處理,不然會出現在某些平臺合成的音頻無法播放的情況。

2.2.3.合成與輸出

  錄音和對背景音樂的處理結束了,接下來就是最后的合成了,對于合成我們腦海中浮現最多的會是什么?相加,對沒錯,音頻合成并不神秘,音頻合成的本質就是相同系數的音頻文件之間數據的加和,當然現實中的合成往往并非如此簡單,在網上搜索"混音算法",我們可以看到大量高深的音頻合成算法,但就目前而言,我們沒必要實現復雜的混音算法,只要讓兩個音頻文件的原始音頻數據相加即可,不過為了讓我們的合成看上去稍微有一些技術含量,此次提供的音頻合成方法中允許任意音頻文件相對于另一音頻文件進行時間上的偏移,并可以通過兩個權重數據進行音量調節。下面我就給出具體代碼吧,講解如何實現。

  1     public static void ComposeAudio(String firstAudioFilePath, String secondAudioFilePath,
  2                                     String composeAudioFilePath, boolean deleteSource,
  3                                     float firstAudioWeight, float secondAudioWeight,
  4                                     int audioOffset,
  5                                     final ComposeAudioInterface composeAudioInterface) {
  6         boolean firstAudioFinish = false;
  7         boolean secondAudioFinish = false;
  8 
  9         byte[] firstAudioByteBuffer;
 10         byte[] secondAudioByteBuffer;
 11         byte[] mp3Buffer;
 12 
 13         short resultShort;
 14         short[] outputShortArray;
 15 
 16         int index;
 17         int firstAudioReadNumber;
 18         int secondAudioReadNumber;
 19         int outputShortArrayLength;
 20         final int byteBufferSize = 1024;
 21 
 22         firstAudioByteBuffer = new byte[byteBufferSize];
 23         secondAudioByteBuffer = new byte[byteBufferSize];
 24         mp3Buffer = new byte[(int) (7200 + (byteBufferSize * 1.25))];
 25 
 26         outputShortArray = new short[byteBufferSize / 2];
 27 
 28         Handler handler = new Handler(Looper.getMainLooper());
 29 
 30         FileInputStream firstAudioInputStream = FileFunction.GetFileInputStreamFromFile(firstAudioFilePath);
 31         FileInputStream secondAudioInputStream = FileFunction.GetFileInputStreamFromFile(secondAudioFilePath);
 32         FileOutputStream composeAudioOutputStream = FileFunction.GetFileOutputStreamFromFile(composeAudioFilePath);
 33 
 34         LameUtil.init(Constant.RecordSampleRate, Constant.LameBehaviorChannelNumber,
 35                 Constant.BehaviorSampleRate, Constant.LameBehaviorBitRate, Constant.LameMp3Quality);
 36 
 37         try {
 38             while (!firstAudioFinish && !secondAudioFinish) {
 39                 index = 0;
 40 
 41                 if (audioOffset < 0) {
 42                     secondAudioReadNumber = secondAudioInputStream.read(secondAudioByteBuffer);
 43 
 44                     outputShortArrayLength = secondAudioReadNumber / 2;
 45 
 46                     for (; index < outputShortArrayLength; index++) {
 47                         resultShort = CommonFunction.GetShort(secondAudioByteBuffer[index * 2],
 48                                 secondAudioByteBuffer[index * 2 + 1], Variable.isBigEnding);
 49 
 50                         outputShortArray[index] = (short) (resultShort * secondAudioWeight);
 51                     }
 52 
 53                     audioOffset += secondAudioReadNumber;
 54 
 55                     if (secondAudioReadNumber < 0) {
 56                         secondAudioFinish = true;
 57                         break;
 58                     }
 59 
 60                     if (audioOffset >= 0) {
 61                         break;
 62                     }
 63                 } else {
 64                     firstAudioReadNumber = firstAudioInputStream.read(firstAudioByteBuffer);
 65 
 66                     outputShortArrayLength = firstAudioReadNumber / 2;
 67 
 68                     for (; index < outputShortArrayLength; index++) {
 69                         resultShort = CommonFunction.GetShort(firstAudioByteBuffer[index * 2],
 70                                 firstAudioByteBuffer[index * 2 + 1], Variable.isBigEnding);
 71 
 72                         outputShortArray[index] = (short) (resultShort * firstAudioWeight);
 73                     }
 74 
 75                     audioOffset -= firstAudioReadNumber;
 76 
 77                     if (firstAudioReadNumber < 0) {
 78                         firstAudioFinish = true;
 79                         break;
 80                     }
 81 
 82                     if (audioOffset <= 0) {
 83                         break;
 84                     }
 85                 }
 86 
 87                 if (outputShortArrayLength > 0) {
 88                     int encodedSize = LameUtil.encode(outputShortArray, outputShortArray,
 89                             outputShortArrayLength, mp3Buffer);
 90 
 91                     if (encodedSize > 0) {
 92                         composeAudioOutputStream.write(mp3Buffer, 0, encodedSize);
 93                     }
 94                 }
 95             }
 96 
 97             handler.post(new Runnable() {
 98                 @Override
 99                 public void run() {
100                     if (composeAudioInterface != null) {
101                         composeAudioInterface.updateComposeProgress(20);
102                     }
103                 }
104             });
105 
106             while (!firstAudioFinish || !secondAudioFinish) {
107                 index = 0;
108 
109                 firstAudioReadNumber = firstAudioInputStream.read(firstAudioByteBuffer);
110                 secondAudioReadNumber = secondAudioInputStream.read(secondAudioByteBuffer);
111 
112                 int minAudioReadNumber = Math.min(firstAudioReadNumber, secondAudioReadNumber);
113                 int maxAudioReadNumber = Math.max(firstAudioReadNumber, secondAudioReadNumber);
114 
115                 if (firstAudioReadNumber < 0) {
116                     firstAudioFinish = true;
117                 }
118 
119                 if (secondAudioReadNumber < 0) {
120                     secondAudioFinish = true;
121                 }
122 
123                 int halfMinAudioReadNumber = minAudioReadNumber / 2;
124 
125                 outputShortArrayLength = maxAudioReadNumber / 2;
126 
127                 for (; index < halfMinAudioReadNumber; index++) {
128                     resultShort = CommonFunction.WeightShort(firstAudioByteBuffer[index * 2],
129                             firstAudioByteBuffer[index * 2 + 1], secondAudioByteBuffer[index * 2],
130                             secondAudioByteBuffer[index * 2 + 1], firstAudioWeight,
131                             secondAudioWeight, Variable.isBigEnding);
132 
133                     outputShortArray[index] = resultShort;
134                 }
135 
136                 if (firstAudioReadNumber != secondAudioReadNumber) {
137                     if (firstAudioReadNumber > secondAudioReadNumber) {
138                         for (; index < outputShortArrayLength; index++) {
139                             resultShort = CommonFunction.GetShort(firstAudioByteBuffer[index * 2],
140                                     firstAudioByteBuffer[index * 2 + 1], Variable.isBigEnding);
141 
142                             outputShortArray[index] = (short) (resultShort * firstAudioWeight);
143                         }
144                     } else {
145                         for (; index < outputShortArrayLength; index++) {
146                             resultShort = CommonFunction.GetShort(secondAudioByteBuffer[index * 2],
147                                     secondAudioByteBuffer[index * 2 + 1], Variable.isBigEnding);
148 
149                             outputShortArray[index] = (short) (resultShort * secondAudioWeight);
150                         }
151                     }
152                 }
153 
154                 if (outputShortArrayLength > 0) {
155                     int encodedSize = LameUtil.encode(outputShortArray, outputShortArray,
156                             outputShortArrayLength, mp3Buffer);
157 
158                     if (encodedSize > 0) {
159                         composeAudioOutputStream.write(mp3Buffer, 0, encodedSize);
160                     }
161                 }
162             }
163         } catch (Exception e) {
164             LogFunction.error("ComposeAudio異常", e);
165 
166             handler.post(new Runnable() {
167                 @Override
168                 public void run() {
169                     if (composeAudioInterface != null) {
170                         composeAudioInterface.composeFail();
171                     }
172                 }
173             });
174 
175             return;
176         }
177 
178         handler.post(new Runnable() {
179             @Override
180             public void run() {
181                 if (composeAudioInterface != null) {
182                     composeAudioInterface.updateComposeProgress(50);
183                 }
184             }
185         });
186 
187         try {
188             final int flushResult = LameUtil.flush(mp3Buffer);
189 
190             if (flushResult > 0) {
191                 composeAudioOutputStream.write(mp3Buffer, 0, flushResult);
192             }
193         } catch (Exception e) {
194             LogFunction.error("釋放ComposeAudio LameUtil異常", e);
195         } finally {
196             try {
197                 composeAudioOutputStream.close();
198             } catch (Exception e) {
199                 LogFunction.error("關閉合成輸出音頻流異常", e);
200             }
201 
202             LameUtil.close();
203         }
204 
205         if (deleteSource) {
206             FileFunction.DeleteFile(firstAudioFilePath);
207             FileFunction.DeleteFile(secondAudioFilePath);
208         }
209 
210         try {
211             firstAudioInputStream.close();
212             secondAudioInputStream.close();
213         } catch (IOException e) {
214             LogFunction.error("關閉合成輸入音頻流異常", e);
215         }
216 
217         handler.post(new Runnable() {
218             @Override
219             public void run() {
220                 if (composeAudioInterface != null) {
221                     composeAudioInterface.composeSuccess();
222                 }
223             }
224         });
225     }

  ComposeAudio方法是此次的進行合成的具體代碼實現,方法的傳入參數中firstAudioFilePath, secondAudioFilePath是用以合成的音頻文件地址,composeAudioFilePath用以指明合成后輸出的MP3文件的存儲地址,firstAudioWeight,secondAudioWeight分別用以指明合成的兩個音頻文件在合成過程中的音量權重,audioOffset用以指明第一個音頻文件相對于第二個音頻文件合成過程中的數據偏移,如為負數,則合成過程中先輸出audioOffset個字節長度的第二個音頻文件數據,如為正數,則合成過程中先輸出audioOffset個字節長度的第一個音頻文件數據,audioOffset在另一程度上也代表著時間的偏移,目前我們合成的兩個音頻文件參數為16位單通道44.1khz采樣率,那么audioOffset如果為1*16/8*1*44100=88200字節,那么最終合成出的MP3文件中會先播放1s的第一個音頻文件的音頻接著再播放兩個音頻文件加和的音頻。

  整體合成代碼是很清晰的,因為加入了時間偏移,所以合成過程中是有可能有一個文件先輸出完的,在代碼中針對性的進行處理即可,當然即使沒有時間偏移也是可能出現類似情況的,比如音樂時長2分鐘,錄音3分鐘,音樂輸出結束后那就只應該輸出錄音音頻了,另外在代碼中將PCM數據編碼為MP3文件使用了LAME的MP3編碼庫,除此以外代碼中就沒有比較復雜的模塊了。

3.總結

  至此,音頻合成的流程我們算是走完了,希望讀到此處的各位對音頻合成的實現有了清晰的了解。

  這篇博文就到這里結束了,本文所有代碼已經托管到https://github.com/CrazyZty/ComposeAudio,大家可以自由下載。

總結

以上是生活随笔為你收集整理的详解如何使用代码进行音频合成的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

日韩另类在线 | 日本特黄一级 | 成人黄色电影免费观看 | 久久久久免费视频 | 亚洲视频电影在线 | 精品久久久久久久久亚洲 | 日韩欧美视频在线播放 | 在线观看日本高清mv视频 | 久久国产精品99久久久久久老狼 | 激情丁香久久 | 在线观看视频国产一区 | 中文字幕在线视频免费播放 | 国产97视频在线 | 国产一级免费在线观看 | 精品国产色 | 正在播放亚洲精品 | 精品女同一区二区三区在线观看 | 久久综合精品一区 | 一区二区三区在线观看中文字幕 | 国产专区日韩专区 | 国产精品麻豆一区二区三区 | 欧美日韩天堂 | 粉嫩av一区二区三区四区 | 在线国产欧美 | 久久亚洲综合国产精品99麻豆的功能介绍 | av在线播放中文字幕 | 日韩欧美一区二区三区视频 | 欧美日本高清视频 | 91视频a | 久久久久久久久久久久国产精品 | 探花在线观看 | 日韩av一区二区三区在线观看 | 69精品视频| av一级在线 | 麻豆91精品91久久久 | 日韩精品久久久久久久电影99爱 | 狠狠色噜噜狠狠 | 久久久久久久久免费 | 麻豆精品传媒视频 | 在线视频手机国产 | 久久久久久久久久久免费 | 天天激情 | 国产又粗又猛又爽又黄的视频免费 | 天天爽夜夜爽人人爽一区二区 | 91在线视频| 中国一级特黄毛片大片久久 | 国产午夜在线 | 少妇bbb好爽| 成人国产网站 | 亚州免费视频 | 久久99久久99精品免观看粉嫩 | 在线免费观看黄 | 免费精品国产va自在自线 | 免费观看一级特黄欧美大片 | 中文字幕国产在线 | 久久综合五月天婷婷伊人 | 国产久草在线观看 | 免费一级日韩欧美性大片 | 中文字幕欧美日韩va免费视频 | 91av免费在线观看 | 丁香婷婷射 | 日本九九视频 | 免费三级黄| 亚洲播播 | 久久综合操 | 天天五月天色 | 精品一区精品二区高清 | 国产精品成人一区二区三区吃奶 | 日韩在线观看视频一区二区三区 | 国产成人精品久久久久 | 天天射天天操天天色 | 深夜国产福利 | 射久久| 狠狠躁夜夜躁人人爽超碰97香蕉 | 精品嫩模福利一区二区蜜臀 | 91精品区| 色婷婷亚洲| 亚洲精品色视频 | 免费成人在线视频网站 | 久久精品综合网 | 超碰在线97观看 | 日韩精品一区二区三区第95 | 超碰在线cao | 91网站在线视频 | 91激情视频在线观看 | 91成人在线视频观看 | 日本中文字幕在线一区 | 992tv在线 | 国产手机视频在线观看 | 精品99在线 | 日本精品一区二区 | 日韩av网站在线播放 | 97**国产露脸精品国产 | 97超视频免费观看 | 一区二区三区在线免费 | 黄色一级免费电影 | 五月婷婷中文网 | 九九精品视频在线观看 | 手机看片中文字幕 | 日韩欧美视频免费在线观看 | 在线看不卡av | 在线观看亚洲精品 | 国产99久久久国产精品免费二区 | 国产亚洲成av片在线观看 | 国产手机在线观看 | 亚州精品天堂中文字幕 | 中文一区在线观看 | 日本爱爱免费视频 | 成人不用播放器 | 天天草天天色 | 一级大片在线观看 | 日本二区三区在线 | a一片一级 | 中文字幕一区二区三区视频 | 久久免费视频这里只有精品 | 五月婷色 | 国产无遮挡又黄又爽馒头漫画 | 亚洲永久精品视频 | 天天五月天色 | 夜夜操天天干 | 欧美二区视频 | 在线观影网站 | 在线观看91视频 | 亚洲最大的av网站 | 亚洲国产精久久久久久久 | 天天躁天天狠天天透 | 91精品久久久久久综合五月天 | 九色精品免费永久在线 | 91av在| 国产在线观看 | 亚洲最新视频在线播放 | 狂野欧美激情性xxxx欧美 | 欧美精品亚洲二区 | 免费99精品国产自在在线 | 国产手机在线观看视频 | 最新成人在线 | 日韩在线大片 | 午夜精品一区二区三区四区 | 探花系列在线 | 欧美日韩色婷婷 | 久久久久日本精品一区二区三区 | 91精品国产成人观看 | 91桃色在线播放 | 激情图片区 | sm免费xx网站 | 天天天天色综合 | 在线观看日韩一区 | 国产精品 日韩 | 波多野结衣在线观看一区二区三区 | 精品国产1区 | 99精品视频在线播放免费 | 玖草影院 | 日韩久久一区二区 | 在线观看91| wwwwww色| 国产精品久久久久久久毛片 | 高清中文字幕av | 久久精品视频国产 | 狠狠操操操 | 免费成人在线网站 | 深夜免费福利在线 | 九九热视频在线播放 | 精品视频免费观看 | 成人av免费播放 | 成年人国产在线观看 | 午夜成人影视 | 99久久婷婷国产综合精品 | 亚洲黄色免费在线 | 国产又粗又猛又色 | 麻豆91精品视频 | 亚洲精品久久久久中文字幕m男 | 久久免费视频在线观看30 | 国产精品99精品久久免费 | 最新国产在线观看 | av综合 日韩| 免费色黄 | 亚洲精品在线观 | 日本性久久| 亚洲精品电影在线 | 超碰97.com| 免费看黄色毛片 | 国产精品一区二区你懂的 | 久久激情综合 | 国产高清一区二区 | 亚洲女人天堂成人av在线 | 最近中文字幕免费av | www.天天草 | 激情久久伊人 | 日日日日日 | 在线免费av电影 | 久久艹在线观看 | 久草视频免费在线观看 | 成人av电影免费在线播放 | 亚洲精品视频在 | 国产精品1区2区3区 久久免费视频7 | 国产精品情侣视频 | 欧美日韩国产精品一区二区亚洲 | 亚洲国产成人在线播放 | 久久96国产精品久久99软件 | 毛片一区二区 | 成人综合日日夜夜 | 成人免费视频网站 | 亚洲 在线 | 黄色一级在线免费观看 | 日韩在线三区 | 欧洲av在线| 婷婷深爱网 | 999久久精品| 91精品在线免费视频 | 免费日韩 精品中文字幕视频在线 | 欧美婷婷色 | 国产视频69| 黄色小视频在线观看免费 | 免费大片av| 成片免费观看视频 | 国产性天天综合网 | 欧洲色吧 | 成人国产网址 | 亚洲精品久久在线 | 免费a视频在线观看 | 欧美另类xxx | 婷婷狠狠操 | 日韩高清一二区 | 超碰人人乐 | 在线观看国产麻豆 | 成人av片免费看 | 日本公乱妇视频 | 国产在线精品一区二区不卡了 | 九九九九九九精品任你躁 | 日本中文字幕一二区观 | 久久99精品久久久久久 | 欧美日韩一区二区在线 | 玖玖玖在线 | 色婷婷久久久综合中文字幕 | 国产99免费 | av在线专区 | 免费av电影网站 | 久精品在线 | www在线观看视频 | 91人人插| 天天综合色网 | 国产精品3区| 99免费在线视频 | 日韩欧美国产成人 | 在线视频一区观看 | 久久色在线观看 | 亚洲日本中文字幕在线观看 | 亚洲国产丝袜在线观看 | 亚洲黄污 | 中文字幕欧美日韩va免费视频 | 日韩精品久久一区二区三区 | 中文字幕一区二区三区在线视频 | 808电影| 亚洲精品国产综合99久久夜夜嗨 | 久久99影院 | 久久久久久久久久电影 | 天天干天天射天天爽 | 日韩视频免费看 | 又粗又长又大又爽又黄少妇毛片 | 黄色网址中文字幕 | av免费在线看网站 | 日韩久久久久久久 | 国产精品都在这里 | 日韩在线视频线视频免费网站 | 免费观看版 | 中文字幕在线视频一区 | 久久久资源| 2020天天干夜夜爽 | 欧美一级日韩三级 | 精品一二三四在线 | 中文字幕视频播放 | 国产伦理精品一区二区 | 国产永久免费观看 | 91在线文字幕| 黄色av成人在线观看 | 国产精品成人自产拍在线观看 | 午夜久久久精品 | 久久久久www | 日韩欧美一区二区三区视频 | 国产黄色免费在线观看 | 欧美久草视频 | 精品国自产在线观看 | 久久草在线精品 | 91亚洲精品国产 | 国产精品岛国久久久久久久久红粉 | 国产69精品久久99不卡的观看体验 | 国产三级精品三级在线观看 | 人人干人人上 | 黄色a级片在线观看 | 久草在线视频国产 | 久久免费视频在线观看 | 天干啦夜天干天干在线线 | 国产视频 亚洲视频 | 国产免费三级在线观看 | 久久综合免费视频 | 欧美一进一出抽搐大尺度视频 | 日本黄色免费网站 | 日韩精品短视频 | 免费在线观看亚洲视频 | 日日操日日插 | 久久综合色婷婷 | 欧美在线观看视频一区二区三区 | 国产精品永久免费在线 | 999成人精品 | 久久精品视频在线播放 | 国产黄在线 | 亚洲国产影院av久久久久 | 亚洲精品在线免费播放 | 8x成人免费视频 | 成片人卡1卡2卡3手机免费看 | 91丨九色丨蝌蚪丨老版 | 亚洲四虎影院 | 91成人精品国产刺激国语对白 | 国产久视频 | 久久99网| 欧美国产精品一区二区 | 久久av影院| 久久er99热精品一区二区 | 91国内在线视频 | 在线看日韩av | 狠狠色综合网站久久久久久久 | 亚洲精品久久久久999中文字幕 | 天天添夜夜操 | 久久日本视频 | 欧美日比视频 | 国内精品免费久久影院 | 中文字幕三区 | 欧美激情视频三区 | 国产99re | 深爱开心激情 | 天天玩天天干 | 99草视频| 色婷婷福利视频 | 精品亚洲va在线va天堂资源站 | 99婷婷狠狠成为人免费视频 | 亚洲激情p | 欧美男同视频网站 | 99草在线视频 | 午夜国产福利在线观看 | 国产精品欧美精品 | 成人免费xyz网站 | 久久在线精品视频 | 99久久精品国产欧美主题曲 | 操操碰| 在线久草视频 | 国产视频999 | 国产第一页精品 | 在线观看一 | www.五月婷 | 在线看免费 | 欧美日韩天堂 | 狠狠的操狠狠的干 | 在线播放国产一区二区三区 | 日韩在线观看三区 | 黄色在线观看免费 | 亚洲精品一区二区三区在线观看 | 亚洲国产视频网站 | www.狠狠操| 国产精品人人做人人爽人人添 | 久久电影色 | 国产视频在线看 | 91在线播放综合 | a在线一区 | 国产福利一区二区三区视频 | 91精品免费在线观看 | 天天插夜夜操 | 日韩一区二区三区不卡 | jizz18欧美18 | 国产精品欧美久久久久无广告 | 久久久久久高潮国产精品视 | 国产精品伦一区二区三区视频 | 亚洲综合网 | 在线观看视频福利 | 国产高清在线观看 | 国产黄色免费在线观看 | 99久久精品国产一区 | 国产激情小视频在线观看 | 免费男女羞羞的视频网站中文字幕 | 天天综合日日夜夜 | 免费成人黄色 | 超碰在线人人97 | 成人精品在线 | 伊人色综合网 | 91视频在线免费下载 | 亚洲精品高清一区二区三区四区 | 手机在线黄色网址 | 午夜精品久久久久久久99婷婷 | 国产真实精品久久二三区 | 久久久首页 | 99免在线观看免费视频高清 | 免费看国产视频 | 亚洲国内在线 | 久久久久久毛片 | 色五月成人 | 欧美99热| 婷婷亚洲五月 | 99精品免费在线观看 | 国产录像在线观看 | 精品亚洲欧美无人区乱码 | 丁香六月国产 | 欧美a性| 欧美日韩国产色综合一二三四 | 中文字幕在线观看91 | 欧美在线观看视频一区二区三区 | 日韩小视频网站 | 天天色综合1 | 中文字幕在线观看第二页 | 亚洲国产午夜精品 | 亚洲va天堂va欧美ⅴa在线 | 亚洲自拍av在线 | 日韩在线第一区 | 日韩在线视频免费看 | 啪啪小视频网站 | 麻豆视频免费在线播放 | 欧美一区中文字幕 | 亚洲黄色三级 | 久久精品99| 国产剧情一区 | 久久香蕉电影网 | 欧美激情综合网 | 欧美一级久久久 | 亚洲va欧美va国产va黑人 | 久久久精品在线观看 | 午夜久久久影院 | 久久久黄色av | 天天狠狠干| 日韩欧美国产免费播放 | 精品资源在线 | 国产黄色片在线免费观看 | 热久久国产精品 | 日本婷婷色 | 狠狠搞,com | 久久天堂网站 | av再线观看| 久久久久久久久久久高潮一区二区 | 国产精品综合久久久久 | 欧美色图亚洲图片 | 97碰碰视频 | 亚洲久草网 | 视频在线观看入口黄最新永久免费国产 | 色综合天天综合网国产成人网 | 99精品国产一区二区三区不卡 | 97操操| 日韩中文字幕免费在线观看 | 国产精品大片 | 亚洲国产精品第一区二区 | 日韩在线观看第一页 | 黄网站免费久久 | 韩国av一区二区三区 | 日本中文字幕电影在线免费观看 | 中文字幕在线观 | 欧美日韩中文国产一区发布 | 国产精品久久久久久久久免费 | 日韩夜夜爽 | 久久综合久久综合这里只有精品 | 免费中文字幕在线观看 | 久久视频在线观看中文字幕 | 高清精品视频 | www.黄色网.com | 成人毛片100免费观看 | 一区中文字幕在线观看 | 精品一区二区三区在线播放 | 亚洲国产精品va在线看黑人 | 在线免费视频一区 | 精品久久久久久久久久久久 | 亚洲精品影视在线观看 | 亚洲视频一级 | 五月天婷婷免费视频 | 国产一级一片免费播放放a 一区二区三区国产欧美 | 久久久久国产精品一区 | 国产精品成人在线观看 | 国产麻豆精品久久一二三 | 在线观看视频免费大全 | 韩日视频在线 | 六月丁香在线视频 | 欧美在线观看视频一区二区三区 | 亚州精品一二三区 | 国产亚洲综合在线 | 亚洲综合欧美精品电影 | 97超视频在线观看 | 亚洲艳情 | 精品视频在线视频 | 欧美性网站 | 亚洲欧美国产日韩在线观看 | 久久精品欧美日韩精品 | 友田真希x88av | 日韩两性视频 | 99热精品国产| 久久精品99国产 | 一级黄色免费网站 | 女人18毛片a级毛片一区二区 | 在线视频app | 久久激情五月激情 | 色噜噜在线观看 | 成人在线网站观看 | 国产中文伊人 | 国偷自产中文字幕亚洲手机在线 | 国产精品久久久久久久久久三级 | 国产色女人| 97香蕉超级碰碰久久免费软件 | 国产精品大全 | 亚洲涩涩涩 | 91精品国产高清自在线观看 | 久久综合成人 | 91九色精品女同系列 | 91精品久久久久久久久久入口 | 91色蜜桃 | 91在线视频导航 | 国产精品第7页 | 91高清视频免费 | 免费网址在线播放 | 欧美精品网站 | 国产亚洲一区二区三区 | a黄色影院 | 国产精品 中文字幕 亚洲 欧美 | 成人影视免费 | 亚洲伦理一区二区 | 日韩精品视频免费 | 成人免费观看电影 | 精品国产成人 | 最新中文字幕视频 | 在线国产能看的 | 中文字幕在线观看不卡 | 欧美在线观看视频一区二区 | 99久久精品国产免费看不卡 | 国产中文字幕视频在线观看 | 色综合久久中文字幕综合网 | 久久社区视频 | 波多野结衣电影一区二区三区 | 日韩精品资源 | 欧美日韩亚洲在线观看 | 成人免费观看视频网站 | 91精品1区 | 国产无遮挡猛进猛出免费软件 | 久久久69| 久久亚洲私人国产精品va | 亚洲精品国精品久久99热一 | 色综合中文字幕 | av免费看在线 | 亚洲jizzjizz日本少妇 | www.91av在线| 国产成人一二三 | 国产精品少妇 | 国产99在线免费 | 国产麻豆精品一区 | 国产高清免费视频 | 中国一级片在线 | 国产视频日本 | 国产美女网 | 日韩在线免费视频观看 | 激情影院在线观看 | 中文字幕五区 | 国产亚洲综合在线 | 免费视频色 | av在线一二三区 | 精品久久影院 | 热re99久久精品国产66热 | 在线v片免费观看视频 | 美女免费网站 | 国产成人精品一区二三区 | 美女视频黄频大全免费 | 国产99在线 | 亚洲黄色av网址 | 国产亚洲精品免费 | 天天插日日插 | 天天色天天爱天天射综合 | 国产在线观看你懂的 | 亚洲天堂在线观看完整版 | 精品久久久一区二区 | 精品久久久久久电影 | 日韩在线国产 | 久久综合亚洲鲁鲁五月久久 | 免费中午字幕无吗 | 日韩最新理论电影 | 免费日韩一级片 | 成人动图 | www.五月婷 | 永久免费在线 | 五月婷婷久久丁香 | 中文字幕 国产视频 | 国产成人一区二区精品非洲 | 黄色三级免费网址 | 少妇bbbb搡bbbb桶 | 欧美精品二 | avwww在线观看| 国内外成人在线 | 91少妇精拍在线播放 | 2021国产在线视频 | 天天爱天天色 | 亚洲九九影院 | 碰天天操天天 | 精品uu| 久久精品99国产 | 久久日韩精品 | 天天色天天 | 国产在线精品国自产拍影院 | 黄色毛片大全 | 日韩视频专区 | 99久久电影| 成人一级片视频 | 婷婷网在线 | 人人射网站| 国产一区欧美日韩 | 天天色天天射天天综合网 | 欧美国产精品久久久久久免费 | 亚洲草视频 | 国产日韩三级 | 午夜精品视频免费在线观看 | 久久国产精品一二三区 | 91福利小视频 | 日韩精品欧美视频 | 欧美 亚洲 另类 激情 另类 | 精品毛片在线 | 日本在线精品视频 | 男女激情免费网站 | a久久免费视频 | 丁香六月五月婷婷 | 免费h漫在线观看 | 亚洲精品视频在线看 | 人人爽久久涩噜噜噜网站 | 亚洲在线视频免费 | 日韩欧美高清视频在线观看 | 九九涩涩av台湾日本热热 | 亚洲精品黄色在线观看 | 亚洲欧洲国产视频 | bbbbb女女女女女bbbbb国产 | 国产美女久久久 | 久久久久久久av | 精品国产一区二区三区在线 | 亚洲成人免费 | 亚洲精品乱码久久久久久蜜桃动漫 | 国产毛片久久久 | 五月精品 | 韩日精品在线 | 国产高清中文字幕 | 日韩精品一区二区免费视频 | 91精品伦理 | 国产精品成人久久 | 97日日碰人人模人人澡分享吧 | 国产精品一区二区精品视频免费看 | 99精品欧美一区二区三区黑人哦 | 黄色av成人在线 | 高潮毛片无遮挡高清免费 | 91资源在线播放 | 久久99久久精品国产 | 91在线视频精品 | 国产自在线 | 日本精品va在线观看 | 久久精品久久精品久久精品 | 国产91小视频 | 中文字幕在线免费观看视频 | 日韩精品久久久 | aaa毛片视频 | 黄网站免费看 | a黄色影院| 精品国产一区二区三区在线 | 中文字幕在线看视频国产中文版 | 亚洲网站在线看 | 久久精品日产第一区二区三区乱码 | 欧美aaa级片| 亚洲成av片人久久久 | 国产免费成人 | 亚洲第一成网站 | 免费高清在线视频一区· | 久久女同性恋中文字幕 | 国产免费二区 | 久久免费黄色网址 | 超碰国产97| 国产精品国产亚洲精品看不卡15 | 99久久99精品| 国产黄色片免费观看 | 久久久精品网站 | 在线国产黄色 | 91视频 - x99av | 成人夜晚看av | 久久视频在线观看免费 | 在线观看一级片 | 欧美在线1区 | 亚洲精品97 | 国产成人久久av977小说 | 中文字幕丰满人伦在线 | 日本精品中文字幕在线观看 | 黄色一级网 | 成 人 黄 色视频免费播放 | 美女网站在线 | 日韩在线精品一区 | 日韩欧美国产精品 | 欧美日韩视频免费看 | 中文字幕乱码一区二区 | 综合色中文| 国产一区二区三区网站 | 国产护士hd高朝护士1 | 亚洲人成在线电影 | 久久成人18免费网站 | 在线视频日韩欧美 | 日本女人的性生活视频 | 99久久99视频只有精品 | 亚洲激情综合 | 国产成人三级一区二区在线观看一 | 免费看的黄网站 | 亚洲第一香蕉视频 | 天天艹天天操 | 在线观看日本高清mv视频 | 天堂在线一区二区 | 在线天堂视频 | 日韩av高清 | 91在线区| 青草视频在线 | 超碰伊人网 | 欧美精品中文在线免费观看 | 在线 你懂| 国产91全国探花系列在线播放 | 久草免费资源 | 免费在线观看成人av | 成人免费观看av | 99热这里是精品 | 日日夜色 | 国产特级毛片aaaaaa高清 | 国产不卡免费av | 国产精品成人一区二区三区吃奶 | 在线免费看黄色 | 国产日韩精品视频 | 免费日韩高清 | 久草久草在线观看 | 天天草天天干 | 麻豆视频在线观看免费 | 免费在线观看黄色网 | 精品国产福利在线 | 国产又粗又硬又爽的视频 | 天天操天天操天天操 | 国产午夜三级一二三区 | 婷婷亚洲激情 | 五月婷色| 97在线视频免费观看 | 国产99久久久国产精品免费二区 | 亚洲精品在线免费观看视频 | 五月婷婷操 | 97香蕉超级碰碰久久免费软件 | 手机av在线免费观看 | 天天综合网在线 | 亚洲欧洲av | 国产午夜麻豆影院在线观看 | 一级片视频在线 | 91经典在线 | 色婷婷一 | 在线精品在线 | 国内精品视频一区二区三区八戒 | 久久五月天色综合 | 国产精品久久久精品 | 精品视频免费观看 | 精品一区 在线 | 国产亚洲成av片在线观看 | 黄色av影院 | 日韩欧美高清一区二区三区 | 欧美久久久影院 | 91秒拍国产福利一区 | 婷色| 亚洲国产精品电影 | 91在线成人| 啪啪凸凸 | 91精品国产乱码在线观看 | 在线免费视频 你懂得 | 国产一级淫片免费看 | 亚洲免费av一区二区 | 欧美久久电影 | 成人黄色免费在线观看 | 国产精品免费久久久久影院仙踪林 | 亚州精品在线视频 | 黄色在线网站噜噜噜 | 欧美成年人在线观看 | 日韩久久精品一区二区三区 | 欧美性春潮 | 色香天天 | 国内99视频 | 不卡中文字幕在线 | 日本精品一区二区三区在线观看 | japanesexxx乱女另类 | 亚洲成人xxx | 欧美一区二区三区在线观看 | 成人免费观看在线视频 | 999久久国产 | 成人网在线免费视频 | 伊人五月天 | 久草精品在线观看 | 三级小视频在线观看 | 99久久99久久 | 干 操 插 | 欧美综合在线观看 | 国产在线欧美在线 | 福利视频区 | 丁香婷婷电影 | 日本久久久久久久久 | 81精品国产乱码久久久久久 | 国产成人精品免费在线观看 | 97国产精品一区二区 | 日韩免费在线一区 | 日韩最新av在线 | 日韩精品一区不卡 | 精品国产成人在线影院 | 91av在线免费看 | 丁香六月av| 欧美在线久久 | 韩国av三级| 爱爱av网站 | 久久男人中文字幕资源站 | 亚洲精品国久久99热 | 丁香婷婷在线观看 | 日本中文字幕在线播放 | 免费a v在线| 精品一区二区综合 | 蜜臀av性久久久久蜜臀aⅴ流畅 | 综合伊人av | av青草 | 免费在线观看成年人视频 | 手机av电影在线观看 | 国产成人精品综合 | 在线韩国电影免费观影完整版 | 四虎国产永久在线精品 | 射久久| 免费日韩视 | 国产精品福利在线播放 | 日本天天色 | 国产精品久久久久永久免费观看 | 亚洲精品乱码久久久久久 | 日日干日日色 | www.亚洲精品视频 | 国产69精品久久久久久 | 麻豆系列在线观看 | 久久综合婷婷国产二区高清 | 超碰97中文| 人人爽人人爽人人爽学生一级 | 天天射天天舔天天干 | 亚洲欧美怡红院 | 亚洲一级影院 | 久久视了 | 久久精品视频国产 | 亚洲九九爱 | 在线观看www91 | 天天色天天操综合网 | 99r精品视频在线观看 | 精品视频免费观看 | 色操插 | 在线观看电影av | 国产免费高清视频 | 色婷婷久久久综合中文字幕 | 97超碰成人 | 91电影福利 | 五月天久久婷婷 | 天天拍天天色 | 亚洲欧美国产精品 | 天天视频色版 | 伊人亚洲精品 | 久久艹国产 | 欧美精品久久久久久久久久 | 六月丁香激情综合 | 国产一级电影在线 | 99久久婷婷国产综合精品 | 日本精品一区二区三区在线观看 | 成人一区二区三区在线观看 | 成人在线观看资源 | 天天操天天综合网 | 欧美a影视 | 99热这里有精品 | 久久99九九99精品 | 国产99久久久国产精品免费看 | 一区二区中文字幕在线观看 | 69亚洲视频 | 国产精品国产毛片 | 夜色资源站国产www在线视频 | 久久在线精品视频 | 国产高清视频免费观看 | 在线中文字幕观看 | 天天干天天干天天 | 在线v片免费观看视频 | 中文字幕在线不卡国产视频 | 色在线视频 | 伊人亚洲精品 | 一区二区三区免费在线 | 日韩中文字幕91 | 麻豆一二 | 久草视频免费观 | av九九九| 亚洲日本色 | 亚洲欧美日本国产 | 菠萝菠萝在线精品视频 | 99精品一区二区三区 | 99电影456麻豆 | 最新免费av在线 | 久久精品96 | 日韩电影一区二区三区在线观看 | 日本精品一区二区在线观看 | 国产在线观看地址 | 91在线小视频 | 天天操·夜夜操 | 国产精品嫩草在线 | 欧美一级电影片 | 国产精品美女视频网站 | 成人午夜电影免费在线观看 | 色资源中文字幕 | 亚洲午夜久久久久久久久 | 中文字幕中文中文字幕 | 国产高清精 | 字幕网av| 国产精品入口麻豆 | 三日本三级少妇三级99 | 亚洲综合欧美日韩狠狠色 | 久久久久伊人 | www.黄色片网站 | 欧美一区日韩精品 | 国产黄色特级片 | 一级特黄aaa大片在线观看 | 国产在线观看99 | 国产精品久久久久久久久婷婷 | 久久久久久久久爱 | 国产成人久久 | 欧美日韩中文国产 | 国内精品久久久久影院男同志 | 免费视频一级片 | 狠狠干天天干 | 在线看毛片网站 | 久久黄色小说视频 | 国产小视频在线 | 国产一区二区三区网站 | 97在线观看免费视频 | 日韩激情视频在线观看 | 日本二区三区在线 | 一级国产视频 | 久久久精品国产一区二区三区 | 欧美精品久久久久久久久老牛影院 | 午夜精品福利一区二区 | 久草精品电影 | 久热av| 国产精品系列在线 | 国产免费亚洲高清 | 久久久久久久久久久精 | 欧美精品视 | 中文字幕免费高清av | 美女视频黄频大全免费 | av一级在线观看 | 中文资源在线播放 | 国产又粗又长又硬免费视频 | 色婷婷视频在线 | 中文字幕一区二 | h视频在线看 | 日一日操一操 | 成人黄色电影在线观看 | 成人手机在线视频 | 中文字幕在线视频免费播放 | 另类老妇性bbwbbw高清 | 日韩久久久久久久 | 久草精品国产 | 欧美精品久久 | 色射色 | 精品国产精品国产偷麻豆 | 青草草在线视频 | www.69xx| 精品国产欧美一区二区 | 97超碰在线免费观看 | 日日夜夜网站 | 一区二区三区国 | 五月婷婷狠狠 | 成人性生交大片免费观看网站 | 国产视频一区二区在线 | 99久久精品国产免费看不卡 | 狠狠色丁香婷婷综合久久片 | 一区二区三区久久精品 | 中文字幕国产 | 日韩av电影中文字幕 | 91精品在线播放 | 日韩精品2区 | 99久在线精品99re8热视频 | 色亚洲网 | 干干夜夜| 亚洲精品看片 | 亚洲在线高清 | 日韩欧美视频一区二区 | 免费黄在线观看 | 国产亚洲视频在线观看 | 一区二区三区在线免费观看视频 | 婷婷综合网 | 一级片免费观看 | 久久视频在线视频 | 色视频在线免费 | 婷婷丁香av | 91福利视频免费观看 | 国产精品视频久久久 | 少妇搡bbbb搡bbb搡忠贞 | 亚洲欧美日本一区二区三区 | 中文字幕免费在线看 | 久久这里只有精品1 | 婷婷社区五月天 | 久久久精品久久日韩一区综合 | 国产香蕉97碰碰碰视频在线观看 | 日韩高清观看 | 国产精品系列在线播放 | 999视频网站 | 国产精品婷婷午夜在线观看 | 91精品在线观看入口 |