├── .classpath
├── .gitignore
├── .project
├── .settings
└── org.eclipse.jdt.core.prefs
├── AndroidManifest.xml
├── LICENSE
├── README.md
├── ic_launcher-web.png
├── preview.png
├── res
├── drawable-hdpi
│ └── ic_launcher.png
├── drawable-mdpi
│ └── ic_launcher.png
├── drawable-xhdpi
│ ├── ic_delete_normal.png
│ ├── ic_delete_pressed.png
│ ├── ic_launcher.png
│ ├── ic_play_normal.png
│ ├── ic_play_pressed.png
│ ├── ic_stop_normal.png
│ └── ic_stop_pressed.png
├── drawable-xxhdpi
│ └── ic_launcher.png
├── drawable
│ ├── ic_delete.xml
│ ├── ic_play.xml
│ ├── ic_stop.xml
│ └── listitem_selector.xml
├── layout
│ ├── activity_compose.xml
│ ├── activity_list.xml
│ ├── activity_main.xml
│ ├── activity_mix_audio.xml
│ └── listitem_audio_info.xml
├── menu
│ └── main.xml
├── values-v11
│ └── styles.xml
├── values-v14
│ └── styles.xml
├── values-w820dp
│ └── dimens.xml
└── values
│ ├── colors.xml
│ ├── dimens.xml
│ ├── strings.xml
│ └── styles.xml
└── src
└── com
└── musicplus
├── .gitignore
├── app
├── MainApplication.java
├── service
│ ├── LocalMusicPlayService.java
│ └── MusicPlayInterface.java
└── ui
│ ├── AudioChooserActivity.java
│ ├── BaseActivity.java
│ ├── ComposeActivity.java
│ ├── MainActivity.java
│ └── MixAudioActivity.java
├── entry
└── AudioEntry.java
├── media
├── AACAudioEncoder.java
├── AndroidAudioDecoder.java
├── AudioDecoder.java
├── AudioEncoder.java
├── CameraHelper.java
├── MediaConstants.java
├── MediaUtils.java
├── MultiAudioMixer.java
├── MultiRawAudioPlayer.java
├── VideoMuxer.java
└── VideoRecorder.java
└── utils
├── DLog.java
└── MD5Util.java
/.classpath:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | #Android generated
2 | bin
3 | gen
4 | proguard
5 |
6 | #IntelliJ IDEA
7 | .idea
8 | *.iml
9 | classes
10 |
11 | #Command line
12 | build.xml
13 | proguard-project.txt
14 |
15 |
16 | build
17 | .gradle
18 |
19 | ##############my configs
20 | # built application files
21 | *.apk
22 | *.ap_
23 |
24 | # files for the dex VM
25 | *.dex
26 |
27 | # Java class files
28 | *.class
29 |
30 | # generated files
31 | bin/
32 | gen/
33 | proguard/
34 |
35 | # Local configuration file (sdk path, etc)
36 | local.properties
37 |
38 | *.pydevproject
39 | .metadata
40 | bin/**
41 | tmp/**
42 | tmp/**/*
43 | *.tmp
44 | *.bak
45 | *.swp
46 | *~.nib
47 | local.properties
48 | .loadpath
49 |
50 | # External tool builders
51 | .externalToolBuilders/
52 |
53 | # Locally stored "Eclipse launch configurations"
54 | *.launch
55 |
56 | # CDT-specific
57 | .cproject
58 |
59 | # PDT-specific
60 | .buildpath
61 | .DS_Store
62 | project.properties
--------------------------------------------------------------------------------
/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | MusicPlus
4 |
5 |
6 |
7 |
8 |
9 | com.android.ide.eclipse.adt.ResourceManagerBuilder
10 |
11 |
12 |
13 |
14 | com.android.ide.eclipse.adt.PreCompilerBuilder
15 |
16 |
17 |
18 |
19 | org.eclipse.jdt.core.javabuilder
20 |
21 |
22 |
23 |
24 | com.android.ide.eclipse.adt.ApkBuilder
25 |
26 |
27 |
28 |
29 |
30 | com.android.ide.eclipse.adt.AndroidNature
31 | org.eclipse.jdt.core.javanature
32 |
33 |
34 |
--------------------------------------------------------------------------------
/.settings/org.eclipse.jdt.core.prefs:
--------------------------------------------------------------------------------
1 | eclipse.preferences.version=1
2 | org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
3 | org.eclipse.jdt.core.compiler.compliance=1.6
4 | org.eclipse.jdt.core.compiler.source=1.6
5 |
--------------------------------------------------------------------------------
/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
22 |
23 |
29 |
30 |
31 |
32 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "{}"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright {yyyy} {name of copyright owner}
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 音频处理另外一个更完整的项目:https://github.com/YeDaxia/AndroidAudioMixer
2 |
3 | # MusicPlus
4 | MusicPlus基于MediaExtractor、MediaMuxer、和MediaCodec等工具类,来实现了对视频中音频的提取,然后和其他音频进行混音后,再用新的音频来合成新的视频。如果你对于音频格式之间的转换感兴趣,比如WAV转ACC格式,也可以在其中找到相关的代码。
5 |
6 | # Note
7 | 1. 由于视频录制的时候会把播放的背景音乐一起录进去,因此在录制包含视频音轨的视频时,记得带上耳机。
8 | 2. 更多介绍: http://www.darcye.com/article/00301860
9 |
10 | # 截图
11 | 
12 |
--------------------------------------------------------------------------------
/ic_launcher-web.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YeDaxia/MusicPlus/34b3c4efa8a59dc155201c6d782d4954a70e5c02/ic_launcher-web.png
--------------------------------------------------------------------------------
/preview.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YeDaxia/MusicPlus/34b3c4efa8a59dc155201c6d782d4954a70e5c02/preview.png
--------------------------------------------------------------------------------
/res/drawable-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YeDaxia/MusicPlus/34b3c4efa8a59dc155201c6d782d4954a70e5c02/res/drawable-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/res/drawable-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YeDaxia/MusicPlus/34b3c4efa8a59dc155201c6d782d4954a70e5c02/res/drawable-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/res/drawable-xhdpi/ic_delete_normal.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YeDaxia/MusicPlus/34b3c4efa8a59dc155201c6d782d4954a70e5c02/res/drawable-xhdpi/ic_delete_normal.png
--------------------------------------------------------------------------------
/res/drawable-xhdpi/ic_delete_pressed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YeDaxia/MusicPlus/34b3c4efa8a59dc155201c6d782d4954a70e5c02/res/drawable-xhdpi/ic_delete_pressed.png
--------------------------------------------------------------------------------
/res/drawable-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YeDaxia/MusicPlus/34b3c4efa8a59dc155201c6d782d4954a70e5c02/res/drawable-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/res/drawable-xhdpi/ic_play_normal.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YeDaxia/MusicPlus/34b3c4efa8a59dc155201c6d782d4954a70e5c02/res/drawable-xhdpi/ic_play_normal.png
--------------------------------------------------------------------------------
/res/drawable-xhdpi/ic_play_pressed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YeDaxia/MusicPlus/34b3c4efa8a59dc155201c6d782d4954a70e5c02/res/drawable-xhdpi/ic_play_pressed.png
--------------------------------------------------------------------------------
/res/drawable-xhdpi/ic_stop_normal.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YeDaxia/MusicPlus/34b3c4efa8a59dc155201c6d782d4954a70e5c02/res/drawable-xhdpi/ic_stop_normal.png
--------------------------------------------------------------------------------
/res/drawable-xhdpi/ic_stop_pressed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YeDaxia/MusicPlus/34b3c4efa8a59dc155201c6d782d4954a70e5c02/res/drawable-xhdpi/ic_stop_pressed.png
--------------------------------------------------------------------------------
/res/drawable-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YeDaxia/MusicPlus/34b3c4efa8a59dc155201c6d782d4954a70e5c02/res/drawable-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/res/drawable/ic_delete.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/res/drawable/ic_play.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/res/drawable/ic_stop.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/res/drawable/listitem_selector.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/res/layout/activity_compose.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
10 |
11 |
15 |
16 |
21 |
22 |
27 |
28 |
34 |
35 |
41 |
42 |
43 |
49 |
50 |
51 |
57 |
58 |
59 |
--------------------------------------------------------------------------------
/res/layout/activity_list.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
--------------------------------------------------------------------------------
/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
10 |
11 |
17 |
18 |
25 |
26 |
33 |
34 |
--------------------------------------------------------------------------------
/res/layout/activity_mix_audio.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
10 |
11 |
17 |
18 |
24 |
25 |
31 |
32 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/res/layout/listitem_audio_info.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
19 |
20 |
30 |
31 |
39 |
40 |
45 |
46 |
--------------------------------------------------------------------------------
/res/menu/main.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/res/values-v11/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/res/values-v14/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 | 64dp
9 |
10 |
11 |
--------------------------------------------------------------------------------
/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FFFFFFFF
4 | #FF000000
5 | #FFCCCCCC
6 |
7 |
--------------------------------------------------------------------------------
/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 16dp
5 | 16dp
6 |
7 |
8 |
--------------------------------------------------------------------------------
/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Music++
5 | Hello world!
6 | Settings
7 |
8 | 录制
9 | 预览
10 | 重录
11 | 添加音轨
12 | 完成录制
13 | 不包含视频音轨
14 | 包含视频音轨
15 | 单纯混音
16 | 可能要耗时比较长,请耐心等待
17 | 合成视频成功
18 | 开始混音
19 | 播放混音
20 | 暂停播放
21 | 继续播放
22 |
23 |
--------------------------------------------------------------------------------
/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
14 |
15 |
16 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/src/com/musicplus/.gitignore:
--------------------------------------------------------------------------------
1 | /tester
2 |
--------------------------------------------------------------------------------
/src/com/musicplus/app/MainApplication.java:
--------------------------------------------------------------------------------
1 | package com.musicplus.app;
2 |
3 | import java.io.File;
4 |
5 | import android.app.Application;
6 | import android.os.Environment;
7 |
8 | public class MainApplication extends Application{
9 |
10 | private static final String SD_ROOT = Environment.getExternalStorageDirectory().getPath();
11 | public static final String APP_EXTERNAL_ROOT_PATH = SD_ROOT + "/MusicPlus";
12 | public static final String TEMP_FILE_PATH = APP_EXTERNAL_ROOT_PATH + "/temp";
13 | public static final String TEMP_AUDIO_PATH = TEMP_FILE_PATH + "/audio";
14 | public static final String TEMP_VIDEO_PATH = TEMP_FILE_PATH + "/video";
15 | public static final String RECORD_AUDIO_PATH = APP_EXTERNAL_ROOT_PATH + "/audio";
16 | public static final String RECORD_VIDEO_PATH = APP_EXTERNAL_ROOT_PATH + "/video";
17 |
18 | @Override
19 | public void onCreate() {
20 | super.onCreate();
21 | createStoreDirs();
22 | }
23 |
24 | private void createStoreDirs(){
25 | File tempAudioDir = new File(TEMP_AUDIO_PATH);
26 | if(!tempAudioDir.exists()){
27 | tempAudioDir.mkdirs();
28 | }
29 | File tempVideoDir = new File(TEMP_VIDEO_PATH);
30 | if(!tempVideoDir.exists()){
31 | tempVideoDir.mkdir();
32 | }
33 | File recordAudioPath = new File(RECORD_AUDIO_PATH);
34 | if(!recordAudioPath.exists()){
35 | recordAudioPath.mkdir();
36 | }
37 | File recordVideoPath = new File(RECORD_VIDEO_PATH);
38 | if(!recordVideoPath.exists()){
39 | recordVideoPath.mkdir();
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/com/musicplus/app/service/LocalMusicPlayService.java:
--------------------------------------------------------------------------------
1 | package com.musicplus.app.service;
2 |
3 | import java.io.IOException;
4 |
5 | import android.app.Service;
6 | import android.content.Context;
7 | import android.content.Intent;
8 | import android.media.AudioManager;
9 | import android.media.MediaPlayer;
10 | import android.media.MediaPlayer.OnPreparedListener;
11 | import android.net.Uri;
12 | import android.os.Binder;
13 | import android.os.IBinder;
14 |
15 | /**
16 | * Local Music Play Service
17 | * @author Darcy
18 | *
19 | */
20 | public class LocalMusicPlayService extends Service implements MusicPlayInterface , OnPreparedListener{
21 |
22 | private AudioFocusHelper mAudioFocusHelper;
23 | private MediaPlayer mAudioPlayer;
24 | private Uri mAudioUri;
25 |
26 | private boolean mPlaying;
27 | private boolean mPrepared;
28 |
29 | @Override
30 | public void onCreate() {
31 | super.onCreate();
32 | mAudioPlayer = new MediaPlayer();
33 | mAudioFocusHelper = new AudioFocusHelper(this,mAudioPlayer);
34 | }
35 |
36 | @Override
37 | public void onDestroy() {
38 | super.onDestroy();
39 | if(mAudioPlayer != null){
40 | mAudioPlayer.release();
41 | mAudioPlayer = null;
42 | }
43 | }
44 |
45 | @Override
46 | public IBinder onBind(Intent intent) {
47 | return new LocalMusicPlayBinder(this);
48 | }
49 |
50 | public static class LocalMusicPlayBinder extends Binder {
51 |
52 | LocalMusicPlayService service;
53 |
54 | LocalMusicPlayBinder(LocalMusicPlayService service){
55 | this.service = service;
56 | }
57 |
58 | public MusicPlayInterface getService() {
59 | return service;
60 | }
61 | }
62 |
63 | @Override
64 | public void play(Uri audioUri) {
65 |
66 | if(mAudioUri != null && mAudioUri.equals(audioUri) && mAudioPlayer.isPlaying()){
67 | mAudioPlayer.start();// start at the beginning
68 | return;
69 | }else{
70 | mAudioUri = audioUri;
71 | }
72 |
73 | mPlaying = false;
74 | mPrepared = false;
75 | mAudioPlayer.reset();
76 | mAudioPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
77 | try {
78 | mAudioPlayer.setOnPreparedListener(this);
79 | mAudioPlayer.setDataSource(this, audioUri);
80 | mAudioPlayer.prepareAsync();
81 | } catch (IllegalArgumentException e) {
82 | e.printStackTrace();
83 | } catch (SecurityException e) {
84 | e.printStackTrace();
85 | } catch (IllegalStateException e) {
86 | e.printStackTrace();
87 | } catch (IOException e) {
88 | e.printStackTrace();
89 | }
90 | }
91 |
92 | @Override
93 | public void pause() {
94 | if(mPlaying){
95 | mAudioFocusHelper.abandonFocus();
96 | mAudioPlayer.pause();
97 | mPlaying = false;
98 | }
99 | }
100 |
101 | @Override
102 | public void onPrepared(MediaPlayer mp) {
103 | mAudioFocusHelper.requestFocus();
104 | mAudioPlayer.start();
105 | mPlaying = true;
106 | mPrepared = true;
107 | }
108 |
109 | @Override
110 | public void close() {
111 | mAudioPlayer.stop();
112 | mAudioPlayer.release();
113 | mPlaying = false;
114 | mPrepared = false;
115 | }
116 |
117 | @Override
118 | public void start() {
119 | if(mPrepared){
120 | mAudioPlayer.start();
121 | }
122 | }
123 |
124 | /**
125 | * Audio Focus
126 | * @author Darcy
127 | * @version android.os.Build.VERSION.SDK_INT >= 8
128 | */
129 | static class AudioFocusHelper implements AudioManager.OnAudioFocusChangeListener {
130 |
131 | private AudioManager mAudioManager;
132 | private MediaPlayer mMediaPlayer;
133 | private Context mContext;
134 |
135 |
136 | public AudioFocusHelper(Context ctx , MediaPlayer player) {
137 | this.mContext = ctx;
138 | this.mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
139 | this.mMediaPlayer = player;
140 | }
141 |
142 | public boolean requestFocus() {
143 | return AudioManager.AUDIOFOCUS_REQUEST_GRANTED ==
144 | mAudioManager.requestAudioFocus(this, AudioManager.STREAM_MUSIC,
145 | AudioManager.AUDIOFOCUS_GAIN);
146 | }
147 |
148 | public boolean abandonFocus() {
149 | return AudioManager.AUDIOFOCUS_REQUEST_GRANTED == mAudioManager.abandonAudioFocus(this);
150 | }
151 |
152 | @Override
153 | public void onAudioFocusChange(int focusChange) {
154 | switch (focusChange) {
155 | case AudioManager.AUDIOFOCUS_GAIN:
156 | if (mMediaPlayer == null){
157 | return;
158 | }else if (!mMediaPlayer.isPlaying()) {
159 | mMediaPlayer.start();
160 | }
161 | mMediaPlayer.setVolume(0.5f, 0.5f);
162 | break;
163 |
164 | case AudioManager.AUDIOFOCUS_LOSS:
165 | if (mMediaPlayer.isPlaying())
166 | mMediaPlayer.stop();
167 | mMediaPlayer.release();
168 | break;
169 | case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
170 | if (mMediaPlayer.isPlaying())
171 | mMediaPlayer.pause();
172 | break;
173 |
174 | case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
175 | if (mMediaPlayer.isPlaying())
176 | mMediaPlayer.setVolume(0.1f, 0.1f);
177 | break;
178 | }
179 |
180 | }
181 | }
182 | }
183 |
--------------------------------------------------------------------------------
/src/com/musicplus/app/service/MusicPlayInterface.java:
--------------------------------------------------------------------------------
1 | package com.musicplus.app.service;
2 |
3 | import android.net.Uri;
4 |
5 | public interface MusicPlayInterface {
6 | /**
7 | * to play an audio
8 | * @param audioUri
9 | */
10 | void play(Uri audioUri);
11 |
12 | /**
13 | * to pause the play
14 | */
15 | void pause();
16 |
17 | /**
18 | * Starts or resumes playback. If playback had previously been paused,
19 | * playback will continue from where it was paused. If playback had been stopped,
20 | * or never started before, playback will start at the beginning.
21 | */
22 | void start();
23 |
24 | /**
25 | * to release the resource
26 | */
27 | void close();
28 | }
29 |
--------------------------------------------------------------------------------
/src/com/musicplus/app/ui/AudioChooserActivity.java:
--------------------------------------------------------------------------------
1 | package com.musicplus.app.ui;
2 |
3 | import java.util.ArrayList;
4 | import java.util.List;
5 |
6 | import android.app.Activity;
7 | import android.content.ComponentName;
8 | import android.content.ContentUris;
9 | import android.content.Context;
10 | import android.content.Intent;
11 | import android.content.ServiceConnection;
12 | import android.database.Cursor;
13 | import android.net.Uri;
14 | import android.os.AsyncTask;
15 | import android.os.Bundle;
16 | import android.os.IBinder;
17 | import android.provider.MediaStore;
18 | import android.support.v7.widget.LinearLayoutManager;
19 | import android.support.v7.widget.RecyclerView;
20 | import android.view.LayoutInflater;
21 | import android.view.View;
22 | import android.view.ViewGroup;
23 | import android.widget.ImageView;
24 | import android.widget.TextView;
25 |
26 | import com.musicplus.R;
27 | import com.musicplus.app.service.LocalMusicPlayService;
28 | import com.musicplus.app.service.MusicPlayInterface;
29 | import com.musicplus.entry.AudioEntry;
30 | import com.musicplus.media.MediaUtils;
31 |
32 | /**
33 | * 选择合成音频
34 | *
35 | * @author Darcy
36 | */
37 | public class AudioChooserActivity extends BaseActivity {
38 |
39 | private static final String[] SUPPORT_DECODE_AUDIOFORMAT = {"audio/mpeg", "audio/x-ms-wma","audio/mp4a-latm" ,"audio/x-wav"};
40 |
41 | private RecyclerView mRvAudio;
42 | private MusicPlayInterface mMisPlayService;
43 |
44 | private static final int PAGE_SIZE = 20;
45 | private int mPage = 1;
46 | private boolean mHasMore = true;
47 |
48 | private ServiceConnection mMisSerciveConn = new ServiceConnection() {
49 | @Override
50 | public void onServiceDisconnected(ComponentName name) {
51 | mMisPlayService = null;
52 | }
53 |
54 | @Override
55 | public void onServiceConnected(ComponentName name, IBinder service) {
56 | mMisPlayService = ((LocalMusicPlayService.LocalMusicPlayBinder)service).getService();
57 | }
58 | };
59 |
60 | private RecyclerView.OnScrollListener mRvScrollListener = new RecyclerView.OnScrollListener(){
61 | public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
62 | if(newState == RecyclerView.SCROLL_STATE_SETTLING){
63 | //加载下页
64 | if(mHasMore){
65 | mPage++;
66 | new GetAudiosTask(AudioChooserActivity.this).execute();
67 | }
68 | }
69 | };
70 | };
71 |
72 | @Override
73 | protected void onCreate(Bundle savedInstanceState) {
74 | super.onCreate(savedInstanceState);
75 | setContentView(R.layout.activity_list);
76 | mRvAudio = findView(R.id.recycler_view);
77 | mRvAudio.setLayoutManager(new LinearLayoutManager(this));
78 | mRvAudio.addOnScrollListener(mRvScrollListener);
79 | new GetAudiosTask(this).execute();
80 | Intent misIntent = new Intent(this,LocalMusicPlayService.class);
81 | bindService(misIntent, mMisSerciveConn, BIND_AUTO_CREATE);
82 | }
83 |
84 | @Override
85 | protected void onStart() {
86 | super.onStart();
87 | if(mMisPlayService != null){
88 | mMisPlayService.start();
89 | }
90 | }
91 |
92 | @Override
93 | protected void onStop() {
94 | super.onStop();
95 | if(mMisPlayService != null){
96 | mMisPlayService.pause();
97 | }
98 | }
99 |
100 | @Override
101 | protected void onDestroy() {
102 | super.onDestroy();
103 | unbindService(mMisSerciveConn);
104 | }
105 |
106 | class GetAudiosTask extends AsyncTask> {
107 |
108 | Context context;
109 |
110 | GetAudiosTask(Context context){
111 | this.context = context;
112 | }
113 |
114 | @Override
115 | protected List doInBackground(Void... params) {
116 | return getLocalAudioes(context, mPage, PAGE_SIZE);
117 | }
118 |
119 | @Override
120 | protected void onPostExecute(List result) {
121 | super.onPostExecute(result);
122 | if(result == null || result.isEmpty()){
123 | mHasMore = false;
124 | }else{
125 | mRvAudio.setAdapter(new AudioInfoAdapter(context,result));
126 | }
127 | }
128 |
129 | /**
130 | * 获取sd卡所有的音频文件
131 | *
132 | * @param context
133 | * @param page 从1开始
134 | * @param context
135 | * @return
136 | * @throws Exception
137 | */
138 | private ArrayList getLocalAudioes(Context context , int page , int pageSize) {
139 |
140 | ArrayList audios = null;
141 |
142 | StringBuilder selectionBuilder = new StringBuilder();
143 | int size = SUPPORT_DECODE_AUDIOFORMAT.length;
144 | for(int i = 0; i != size ; ++i){
145 | selectionBuilder.append("mime_type=? or ");
146 | }
147 | int sbLen = selectionBuilder.length();
148 | selectionBuilder.delete(sbLen - 3, sbLen);
149 |
150 | final String selection = selectionBuilder.toString();
151 | final String orderBy = String.format("%s LIMIT %s , %s ",MediaStore.Audio.Media.DEFAULT_SORT_ORDER, (page-1)*pageSize , pageSize);
152 |
153 | Cursor cursor = context.getContentResolver().query(
154 | MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
155 | new String[] { MediaStore.Audio.Media._ID,
156 | MediaStore.Audio.Media.DISPLAY_NAME,
157 | MediaStore.Audio.Media.TITLE,
158 | MediaStore.Audio.Media.DURATION,
159 | MediaStore.Audio.Media.ARTIST,
160 | MediaStore.Audio.Media.ALBUM,
161 | MediaStore.Audio.Media.YEAR,
162 | MediaStore.Audio.Media.MIME_TYPE,
163 | MediaStore.Audio.Media.SIZE,
164 | MediaStore.Audio.Media.DATA },
165 | selection,
166 | SUPPORT_DECODE_AUDIOFORMAT, orderBy);
167 |
168 | audios = new ArrayList();
169 |
170 | if (cursor.moveToFirst()) {
171 |
172 | AudioEntry audioEntry = null;
173 |
174 | String fileUrl = null;
175 | boolean isMatchAudioFormat = false;
176 | do {
177 | fileUrl = cursor.getString(9);
178 | isMatchAudioFormat = MediaUtils.isMatchAudioFormat(fileUrl, 44100, 2);
179 | if(!isMatchAudioFormat){
180 | continue;
181 | }
182 |
183 | audioEntry = new AudioEntry();
184 | audioEntry.id = cursor.getLong(0);
185 | // 文件名
186 | audioEntry.fileName = cursor.getString(1);
187 | // 歌曲名
188 | audioEntry.title = cursor.getString(2);
189 | // 时长
190 | audioEntry.duration = cursor.getInt(3);
191 | // 歌手名
192 | audioEntry.artist = cursor.getString(4);
193 | // 专辑名
194 | audioEntry.album = cursor.getString(5);
195 | // 年代
196 | audioEntry.year = cursor.getString(6);
197 | // 歌曲格式
198 | audioEntry.mime = cursor.getString(7).trim();
199 | // 文件大小
200 | audioEntry.size = cursor.getString(8);
201 | // 文件路径
202 | audioEntry.fileUrl = fileUrl;
203 | audios.add(audioEntry);
204 | } while (cursor.moveToNext());
205 |
206 | cursor.close();
207 |
208 | }
209 | return audios;
210 | }
211 | }
212 |
213 | class AudioInfoAdapter extends RecyclerView.Adapter{
214 |
215 | List audioList;
216 | LayoutInflater layoutInflater;
217 | AudioEntry lastPlayingAudio;
218 | AudioInfoViewHolder lastPlayingViewHodler;
219 |
220 | AudioInfoAdapter(Context context , List audioList){
221 | this.audioList = audioList;
222 | this.layoutInflater= LayoutInflater.from(context);
223 | }
224 |
225 | @Override
226 | public int getItemCount() {
227 | return audioList == null ? 0 :audioList.size();
228 | }
229 |
230 | @Override
231 | public void onBindViewHolder(AudioInfoViewHolder viewHolder, int position) {
232 | AudioEntry audioEntry = audioList.get(position);
233 | viewHolder.tvArtist.setText(audioEntry.artist);
234 | viewHolder.tvFileName.setText(audioEntry.fileName);
235 | viewHolder.playClickListener.audioEntry = audioEntry;
236 | viewHolder.playClickListener.viewHoler = viewHolder;
237 | viewHolder.itemClickListener.audioEntry = audioEntry;
238 | if(audioEntry.isPlaying){
239 | viewHolder.ivPlay.setImageResource(R.drawable.ic_stop);
240 | }else{
241 | viewHolder.ivPlay.setImageResource(R.drawable.ic_play);
242 | }
243 | }
244 |
245 | @Override
246 | public AudioInfoViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
247 | return new AudioInfoViewHolder(layoutInflater.inflate(R.layout.listitem_audio_info, parent, false));
248 | }
249 |
250 | class AudioInfoViewHolder extends RecyclerView.ViewHolder {
251 |
252 | TextView tvFileName;
253 | TextView tvArtist;
254 | ImageView ivPlay;
255 | OnPlayClickListener playClickListener;
256 | OnItemClickListener itemClickListener;
257 |
258 | public AudioInfoViewHolder(View view) {
259 | super(view);
260 | tvFileName = (TextView) view.findViewById(R.id.tv_file_name);
261 | tvArtist = (TextView) view.findViewById(R.id.tv_artist);
262 | ivPlay = (ImageView) view.findViewById(R.id.iv_play);
263 | playClickListener = new OnPlayClickListener();
264 | ivPlay.setOnClickListener(playClickListener);
265 | itemClickListener = new OnItemClickListener();
266 | view.setOnClickListener(itemClickListener);
267 | }
268 |
269 | }
270 |
271 | class OnPlayClickListener implements View.OnClickListener{
272 |
273 | AudioEntry audioEntry;
274 |
275 | AudioInfoViewHolder viewHoler;
276 |
277 | void setItemHolder(AudioEntry audio,AudioInfoViewHolder viewHolder){
278 | this.audioEntry = audio;
279 | this.viewHoler = viewHolder;
280 | }
281 |
282 | @Override
283 | public void onClick(View v) {
284 | if(audioEntry.isPlaying){
285 | mMisPlayService.pause();
286 | audioEntry.isPlaying = false;
287 | viewHoler.ivPlay.setImageResource(R.drawable.ic_play);
288 | }else{
289 | Uri audioUri = ContentUris.withAppendedId(android.provider.MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, audioEntry.id);
290 | mMisPlayService.play(audioUri);
291 | audioEntry.isPlaying = true;
292 | if(lastPlayingAudio != null && lastPlayingViewHodler != null){
293 | lastPlayingAudio.isPlaying = false;
294 | lastPlayingViewHodler.ivPlay.setImageResource(R.drawable.ic_play);
295 | }
296 | lastPlayingAudio = audioEntry;
297 | lastPlayingViewHodler = viewHoler;
298 | viewHoler.ivPlay.setImageResource(R.drawable.ic_stop);
299 | }
300 | }
301 |
302 | }
303 |
304 | class OnItemClickListener implements View.OnClickListener{
305 | AudioEntry audioEntry;
306 |
307 | void setItemHolder(AudioEntry audio){
308 | this.audioEntry = audio;
309 | }
310 |
311 | @Override
312 | public void onClick(View v) {
313 | Activity context = AudioChooserActivity.this;
314 | Intent okData = new Intent();
315 | okData.putExtra("audio", audioEntry);
316 | context.setResult(RESULT_OK, okData);
317 | context.finish();
318 | }
319 | }
320 | }
321 |
322 | }
323 |
--------------------------------------------------------------------------------
/src/com/musicplus/app/ui/BaseActivity.java:
--------------------------------------------------------------------------------
1 | package com.musicplus.app.ui;
2 |
3 | import android.app.Activity;
4 | import android.view.View;
5 |
6 | abstract class BaseActivity extends Activity{
7 |
8 | @SuppressWarnings("unchecked")
9 | public T findView(int id) {
10 | return (T) findViewById(id);
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/com/musicplus/app/ui/ComposeActivity.java:
--------------------------------------------------------------------------------
1 | package com.musicplus.app.ui;
2 |
3 | import java.io.File;
4 | import java.io.FileInputStream;
5 | import java.io.FileNotFoundException;
6 | import java.io.FileOutputStream;
7 | import java.io.IOException;
8 | import java.util.HashSet;
9 | import java.util.Set;
10 | import java.util.concurrent.CyclicBarrier;
11 |
12 | import android.app.ProgressDialog;
13 | import android.content.Intent;
14 | import android.media.AudioManager;
15 | import android.media.MediaPlayer;
16 | import android.os.AsyncTask;
17 | import android.os.Bundle;
18 | import android.view.Surface;
19 | import android.view.SurfaceHolder;
20 | import android.view.SurfaceView;
21 | import android.view.TextureView;
22 | import android.view.View;
23 | import android.view.ViewGroup.LayoutParams;
24 | import android.widget.Button;
25 | import android.widget.ImageView;
26 | import android.widget.LinearLayout;
27 | import android.widget.TextView;
28 | import android.widget.Toast;
29 |
30 | import com.musicplus.R;
31 | import com.musicplus.app.MainApplication;
32 | import com.musicplus.entry.AudioEntry;
33 | import com.musicplus.media.AudioDecoder;
34 | import com.musicplus.media.AudioDecoder.OnAudioDecoderListener;
35 | import com.musicplus.media.MultiRawAudioPlayer;
36 | import com.musicplus.media.MultiRawAudioPlayer.OnRawAudioPlayListener;
37 | import com.musicplus.media.VideoMuxer;
38 | import com.musicplus.media.VideoRecorder;
39 | import com.musicplus.media.VideoRecorder.OnVideoRecordListener;
40 | import com.musicplus.utils.MD5Util;
41 |
42 | /**
43 | *
44 | * follow the steps below:
45 | *
46 | * choose music as video's audio track
47 | * begin to record video
48 | * finish the recording
49 | *
50 | * @author Darcy
51 | */
52 | public class ComposeActivity extends BaseActivity implements View.OnClickListener, OnRawAudioPlayListener{
53 |
54 | private final static String TEMP_RECORD_VIDEO_FILE = MainApplication.TEMP_VIDEO_PATH + "/temp_record_video";
55 | private final static String FINAL_MIX_VIDEO_FILE = MainApplication.RECORD_VIDEO_PATH + "/final_mix_video.mp4";
56 |
57 | private final static int RECORD_STATE_INITIAL = 0x0;
58 | private final static int RECORD_STATE_PREPARING = 0x1;
59 | private final static int RECORD_STATE_RECORDING = 0x2;
60 | private final static int RECORD_STATE_DONE = 0x3;
61 |
62 | private final static int REQUEST_CODE_ADD_MUSIC = 0x1;
63 |
64 | public final static String EX_INCLUDE_VIDEO_AUDIO = "include-video-audio";
65 |
66 |
67 | private SurfaceView svVideoPreview;
68 | private Button btnRecord;
69 | private Button btnAddMusic;
70 | private Button btnPreview;
71 | private Button btnRedoRecord;
72 | private LinearLayout containerAudioTracks;
73 | private VideoRecorder videoRecorder;
74 | private Set addAudioTracks = new HashSet();
75 | private MultiRawAudioPlayer mBackMisicPlayer;
76 | private String mTempMixAudioFile;
77 | private CyclicBarrier recordBarrier = new CyclicBarrier(2);
78 | private ProgressDialog dlgDecoding;
79 | private ProgressDialog dlgMuxing;
80 | private MediaPlayer videoPlayer;
81 | private String finalMixVideo;
82 |
83 | private int recordState;
84 | private boolean isIncludeVideoAudio = false;
85 | private static final int MAX_PROGRESS = 100;
86 |
87 | @Override
88 | protected void onCreate(Bundle savedInstanceState) {
89 | super.onCreate(savedInstanceState);
90 | setContentView(R.layout.activity_compose);
91 | svVideoPreview = findView(R.id.sv_video_preview);
92 | btnRecord = findView(R.id.btn_record);
93 | btnRecord.setOnClickListener(this);
94 | btnAddMusic = findView(R.id.btn_add_music);
95 | btnAddMusic.setOnClickListener(this);
96 | containerAudioTracks = findView(R.id.container_musics);
97 | btnPreview = findView(R.id.btn_preview);
98 | btnPreview.setOnClickListener(this);
99 | btnRedoRecord = findView(R.id.btn_re_record);
100 | btnRedoRecord.setOnClickListener(this);
101 |
102 | videoRecorder = new VideoRecorder(this, svVideoPreview, TEMP_RECORD_VIDEO_FILE,recordBarrier);
103 | recordState = RECORD_STATE_INITIAL;
104 |
105 | isIncludeVideoAudio = getIntent().getBooleanExtra(EX_INCLUDE_VIDEO_AUDIO, false);
106 |
107 | LayoutParams lpPre = svVideoPreview.getLayoutParams();
108 | lpPre.height = 1080 * 640 / 480;
109 | svVideoPreview.setLayoutParams(lpPre);
110 |
111 | dlgDecoding = new ProgressDialog(this);
112 | dlgDecoding.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
113 | dlgDecoding.setCancelable(false);
114 | dlgDecoding.setCanceledOnTouchOutside(false);
115 | dlgDecoding.setMax(MAX_PROGRESS);
116 |
117 | dlgMuxing = new ProgressDialog(this);
118 | dlgMuxing.setProgressStyle(ProgressDialog.STYLE_SPINNER);
119 | dlgMuxing.setCancelable(false);
120 | dlgMuxing.setCanceledOnTouchOutside(false);
121 | }
122 |
123 | @Override
124 | public void onClick(View v) {
125 | switch(v.getId()){
126 | case R.id.btn_record:
127 | performRecord();
128 | break;
129 | case R.id.btn_add_music:
130 | performAddMusic();
131 | break;
132 | case R.id.btn_preview:
133 | performPreview();
134 | break;
135 | case R.id.btn_re_record:
136 | performRedoRecord();
137 | break;
138 | }
139 | }
140 |
141 | @Override
142 | protected void onPause() {
143 | super.onPause();
144 | videoRecorder.release();
145 | }
146 |
147 | @Override
148 | protected void onResume() {
149 | super.onResume();
150 | svVideoPreview.postDelayed(new Runnable(){
151 | @Override
152 | public void run() {
153 | videoRecorder.startPreview();
154 | }
155 | }, 100);
156 | }
157 |
158 | @Override
159 | protected void onStop() {
160 | super.onStop();
161 | stopBackgroundMusic();
162 | }
163 |
164 | @Override
165 | protected void onActivityResult(int requestCode, int resultCode, Intent data) {
166 | super.onActivityResult(requestCode, resultCode, data);
167 | if(requestCode == REQUEST_CODE_ADD_MUSIC && resultCode == RESULT_OK){
168 | dlgDecoding.show();
169 | AudioEntry audioEntry = (AudioEntry) data.getSerializableExtra("audio");
170 | new DecodeAudioTask(audioEntry).execute();
171 | }
172 | }
173 |
174 | private void performRecord(){
175 | if(recordState == RECORD_STATE_INITIAL){
176 | recordState = RECORD_STATE_PREPARING;
177 | videoRecorder.setOnVideoRecordListener(new OnVideoRecordListener() {
178 | @Override
179 | public void onStarted() {
180 | recordState = RECORD_STATE_RECORDING;
181 | btnRecord.setText(R.string.complete_record);
182 | }
183 |
184 | public void onError(int errorCode) {
185 | }
186 | });
187 | videoRecorder.startRecord();
188 | playBackgroundMusic();
189 | }else if(recordState == RECORD_STATE_RECORDING){
190 | recordState = RECORD_STATE_DONE;
191 | dlgMuxing.setMessage(getString(R.string.please_wait));
192 | dlgMuxing.show();
193 | videoRecorder.release();
194 | recordBarrier.reset();
195 | stopBackgroundMusic();
196 | btnRecord.postDelayed(new Runnable() {
197 | @Override
198 | public void run() {
199 | final boolean hasBackgroundMisic = hasBackgroundMisic();
200 | if(hasBackgroundMisic){
201 | new MixVideoAndAudioTask().execute();
202 | }else{
203 | finalMixVideo = TEMP_RECORD_VIDEO_FILE;
204 | }
205 | }
206 | }, 500);
207 | }
208 | }
209 |
210 | @Override
211 | public void onPlayStart() {
212 | videoRecorder.startRecord();
213 | }
214 |
215 | @Override
216 | public void onPlayStop(String tempMixFile) {
217 | mTempMixAudioFile = tempMixFile;
218 | }
219 |
220 | @Override
221 | public void onPlayComplete(String tempMixFile) {
222 | mTempMixAudioFile = tempMixFile;
223 | }
224 |
225 | private void performAddMusic(){
226 | Intent addMusicIntent = new Intent(this,AudioChooserActivity.class);
227 | startActivityForResult(addMusicIntent, REQUEST_CODE_ADD_MUSIC);
228 | }
229 |
230 | private void performRedoRecord(){
231 | recordState = RECORD_STATE_INITIAL;
232 | btnRecord.setText(R.string.record);
233 | videoRecorder.release();
234 | videoRecorder.startPreview();
235 | }
236 |
237 | private void performPreview(){
238 | if(svVideoPreview != null && finalMixVideo != null){
239 | new VideoPlayTask(svVideoPreview, finalMixVideo).execute();
240 | }
241 | }
242 |
243 | private void playBackgroundMusic(){
244 | int trackSize = addAudioTracks.size();
245 | if(trackSize > 0){
246 | mBackMisicPlayer = new MultiRawAudioPlayer(addAudioTracks.toArray(new AudioEntry[trackSize]),recordBarrier);
247 | mBackMisicPlayer.setOnRawAudioPlayListener(this);
248 | mBackMisicPlayer.play();
249 | }
250 | }
251 |
252 | private void stopBackgroundMusic(){
253 | if(mBackMisicPlayer!=null)
254 | mBackMisicPlayer.stop();
255 | }
256 |
257 | private boolean hasBackgroundMisic(){
258 | return addAudioTracks != null && addAudioTracks.size() > 0;
259 | }
260 |
261 | class MixVideoAndAudioTask extends AsyncTask{
262 |
263 | @Override
264 | protected Boolean doInBackground(Void... params) {
265 | finalMixVideo = FINAL_MIX_VIDEO_FILE;
266 | VideoMuxer videoMuxer = VideoMuxer.createVideoMuxer(finalMixVideo);
267 | videoMuxer.mixRawAudio(new File(TEMP_RECORD_VIDEO_FILE), new File(mTempMixAudioFile), isIncludeVideoAudio);
268 | return true;
269 | }
270 |
271 | @Override
272 | protected void onPostExecute(Boolean result) {
273 | super.onPostExecute(result);
274 | dlgMuxing.cancel();
275 | Toast.makeText(ComposeActivity.this, getString(R.string.mix_video_success), Toast.LENGTH_SHORT).show();
276 | }
277 | }
278 |
279 | class DecodeAudioTask extends AsyncTask{
280 |
281 | AudioEntry decAudio;
282 |
283 | DecodeAudioTask(AudioEntry decAudio){
284 | this.decAudio = decAudio;
285 | }
286 |
287 | @Override
288 | protected Boolean doInBackground(Void... params) {
289 | String decodeFilePath = MainApplication.TEMP_AUDIO_PATH + "/" + MD5Util.getMD5Str(decAudio.fileUrl);
290 | File decodeFile = new File(decodeFilePath);
291 | if(decodeFile.exists()){
292 | publishProgress(1.0);
293 | decAudio.fileUrl = decodeFilePath;
294 | return true;
295 | }
296 |
297 | if(decAudio.mime.contains("x-ms-wma")){
298 | FileInputStream fisWavFile = null;
299 | FileOutputStream fosRawAudioFile = null;
300 | try {
301 | File srcAudioFile = new File(decAudio.fileUrl);
302 | long audioFileSize = srcAudioFile.length();
303 | fisWavFile = new FileInputStream(srcAudioFile);
304 | fosRawAudioFile = new FileOutputStream(decodeFile);
305 | fisWavFile.read(new byte[44]);
306 | byte[] rawBuf = new byte[1024];
307 | int readCount;
308 | double totalReadCount = 44;
309 | while((readCount = fisWavFile.read(rawBuf)) != -1){
310 | fosRawAudioFile.write(rawBuf, 0, readCount);
311 | totalReadCount += readCount;
312 | publishProgress(totalReadCount/audioFileSize);
313 | }
314 | publishProgress(1.0);
315 | return true;
316 | } catch (FileNotFoundException e) {
317 | e.printStackTrace();
318 | } catch (IOException e) {
319 | e.printStackTrace();
320 | }finally{
321 | try {
322 | if(fisWavFile != null)
323 | fisWavFile.close();
324 |
325 | if(fosRawAudioFile != null)
326 | fosRawAudioFile.close();
327 | } catch (IOException e) {
328 | e.printStackTrace();
329 | }
330 | }
331 | return false;
332 | }else{
333 | AudioDecoder audioDec = AudioDecoder.createDefualtDecoder(decAudio.fileUrl);
334 | try {
335 | decAudio.fileUrl = decodeFilePath;
336 | audioDec.setOnAudioDecoderListener(new OnAudioDecoderListener() {
337 | @Override
338 | public void onDecode(byte[] decodedBytes, double progress)throws IOException {
339 | publishProgress(progress);
340 | }
341 | });
342 | audioDec.decodeToFile(decodeFilePath);
343 | return true;
344 | } catch (IOException e) {
345 | e.printStackTrace();
346 | return false;
347 | }
348 | }
349 | }
350 |
351 | @Override
352 | protected void onProgressUpdate(Double... values) {
353 | super.onProgressUpdate(values);
354 | dlgDecoding.setProgress((int) (MAX_PROGRESS * values[0]));
355 | }
356 |
357 | @Override
358 | protected void onPostExecute(Boolean result) {
359 | super.onPostExecute(result);
360 | if(result){
361 | addMisicTrack(decAudio);
362 | }
363 | dlgDecoding.cancel();
364 | }
365 |
366 | private void addMisicTrack(final AudioEntry decAudio){
367 | if(addAudioTracks.contains(decAudio))
368 | return;
369 |
370 | addAudioTracks.add(decAudio);
371 | final View viewTrack = View.inflate(ComposeActivity.this, R.layout.listitem_audio_info, null);
372 | TextView tvName = (TextView)viewTrack.findViewById(R.id.tv_file_name);
373 | TextView tvArtist = (TextView)viewTrack.findViewById(R.id.tv_artist);
374 | ImageView tvDel = (ImageView)viewTrack.findViewById(R.id.iv_play);
375 | tvName.setText(decAudio.fileName);
376 | tvArtist.setText(decAudio.artist);
377 | tvDel.setImageResource(R.drawable.ic_delete);
378 | tvDel.setOnClickListener(new View.OnClickListener() {
379 | @Override
380 | public void onClick(View v) {
381 | addAudioTracks.remove(decAudio);
382 | containerAudioTracks.removeView(viewTrack);
383 | }
384 | });
385 |
386 | containerAudioTracks.addView(viewTrack);
387 | }
388 | }
389 |
390 | class VideoPlayTask extends AsyncTask{
391 |
392 | private SurfaceView surfaceView;
393 |
394 | private String vidoeFile;
395 |
396 | VideoPlayTask(SurfaceView surfaceView,String vidoeFile){
397 | this.surfaceView = surfaceView;
398 | this.vidoeFile = vidoeFile;
399 | }
400 |
401 | @Override
402 | protected Boolean doInBackground(Void... params) {
403 | try {
404 | if(videoPlayer == null){
405 | videoPlayer = new MediaPlayer();
406 | }else{
407 | videoPlayer.reset();
408 | }
409 | videoPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
410 | videoPlayer.setDisplay(surfaceView.getHolder());
411 | videoPlayer.setDataSource(vidoeFile);
412 | videoPlayer.prepare();
413 | return true;
414 | } catch (Exception e) {
415 | e.printStackTrace();
416 | videoPlayer.release();
417 | return false;
418 | }
419 | }
420 |
421 | @Override
422 | protected void onPostExecute(Boolean result) {
423 | super.onPostExecute(result);
424 | if(result){
425 | videoPlayer.start();
426 | }
427 | }
428 | }
429 | }
430 |
--------------------------------------------------------------------------------
/src/com/musicplus/app/ui/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.musicplus.app.ui;
2 |
3 | import android.content.Intent;
4 | import android.os.Bundle;
5 | import android.view.View;
6 |
7 | import com.musicplus.R;
8 |
9 | public class MainActivity extends BaseActivity{
10 |
11 | @Override
12 | protected void onCreate(Bundle savedInstanceState) {
13 | super.onCreate(savedInstanceState);
14 | setContentView(R.layout.activity_main);
15 | }
16 |
17 | public void onExcludeClick(View v){
18 | Intent composeIntent = new Intent(this, ComposeActivity.class);
19 | composeIntent.putExtra(ComposeActivity.EX_INCLUDE_VIDEO_AUDIO, false);
20 | startActivity(composeIntent);
21 | }
22 |
23 | public void onIncludeClick(View v){
24 | Intent composeIntent = new Intent(this, ComposeActivity.class);
25 | composeIntent.putExtra(ComposeActivity.EX_INCLUDE_VIDEO_AUDIO, true);
26 | startActivity(composeIntent);
27 | }
28 |
29 | public void onMixAudioClick(View v){
30 | Intent mixAudioIntent = new Intent(this, MixAudioActivity.class);
31 | startActivity(mixAudioIntent);
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/com/musicplus/app/ui/MixAudioActivity.java:
--------------------------------------------------------------------------------
1 | package com.musicplus.app.ui;
2 |
3 | import java.io.File;
4 | import java.io.FileInputStream;
5 | import java.io.FileNotFoundException;
6 | import java.io.FileOutputStream;
7 | import java.io.IOException;
8 | import java.util.HashSet;
9 | import java.util.Set;
10 |
11 | import android.app.ProgressDialog;
12 | import android.content.ComponentName;
13 | import android.content.Intent;
14 | import android.content.ServiceConnection;
15 | import android.net.Uri;
16 | import android.os.AsyncTask;
17 | import android.os.Bundle;
18 | import android.os.IBinder;
19 | import android.view.View;
20 | import android.widget.Button;
21 | import android.widget.ImageView;
22 | import android.widget.LinearLayout;
23 | import android.widget.TextView;
24 | import android.widget.Toast;
25 |
26 | import com.musicplus.R;
27 | import com.musicplus.app.MainApplication;
28 | import com.musicplus.app.service.LocalMusicPlayService;
29 | import com.musicplus.app.service.MusicPlayInterface;
30 | import com.musicplus.entry.AudioEntry;
31 | import com.musicplus.media.AudioDecoder;
32 | import com.musicplus.media.AudioEncoder;
33 | import com.musicplus.media.AudioDecoder.OnAudioDecoderListener;
34 | import com.musicplus.media.MultiAudioMixer;
35 | import com.musicplus.media.MultiAudioMixer.OnAudioMixListener;
36 | import com.musicplus.utils.MD5Util;
37 |
38 | public class MixAudioActivity extends BaseActivity implements
39 | View.OnClickListener {
40 |
41 | private final static int REQUEST_CODE_ADD_MUSIC = 0x1;
42 | private static final int MAX_PROGRESS = 100;
43 |
44 | private Button btnAddMusic;
45 | private Button btnMix;
46 | private Button btnPlayMixAudio;
47 | private LinearLayout containerAudioTracks;
48 | private ProgressDialog dlgDecoding;
49 | private ProgressDialog dlgMixing;
50 | private String mFinalMixAudioFile;
51 |
52 | private Set addAudioTracks = new HashSet();
53 |
54 | private MusicPlayInterface mMisPlayService;
55 |
56 | private ServiceConnection mMisSerciveConn = new ServiceConnection() {
57 | @Override
58 | public void onServiceDisconnected(ComponentName name) {
59 | mMisPlayService = null;
60 | }
61 |
62 | @Override
63 | public void onServiceConnected(ComponentName name, IBinder service) {
64 | mMisPlayService = ((LocalMusicPlayService.LocalMusicPlayBinder)service).getService();
65 | }
66 | };
67 |
68 | @Override
69 | protected void onCreate(Bundle savedInstanceState) {
70 | super.onCreate(savedInstanceState);
71 | setContentView(R.layout.activity_mix_audio);
72 | btnAddMusic = findView(R.id.btn_add_music);
73 | btnMix = findView(R.id.btn_mix_audio);
74 | containerAudioTracks = findView(R.id.container_musics);
75 | btnPlayMixAudio = findView(R.id.btn_play_mix_audio);
76 | btnAddMusic.setOnClickListener(this);
77 | btnMix.setOnClickListener(this);
78 | btnPlayMixAudio.setOnClickListener(this);
79 |
80 | dlgDecoding = new ProgressDialog(this);
81 | dlgDecoding.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
82 | dlgDecoding.setCancelable(false);
83 | dlgDecoding.setCanceledOnTouchOutside(false);
84 | dlgDecoding.setMax(MAX_PROGRESS);
85 |
86 | dlgMixing = new ProgressDialog(this);
87 | dlgMixing.setProgressStyle(ProgressDialog.STYLE_SPINNER);
88 | dlgMixing.setCancelable(false);
89 | dlgMixing.setCanceledOnTouchOutside(false);
90 |
91 | Intent misIntent = new Intent(this,LocalMusicPlayService.class);
92 | bindService(misIntent, mMisSerciveConn, BIND_AUTO_CREATE);
93 | }
94 |
95 | @Override
96 | protected void onDestroy() {
97 | super.onDestroy();
98 | unbindService(mMisSerciveConn);
99 | }
100 |
101 | @Override
102 | public void onClick(View v) {
103 | switch (v.getId()) {
104 | case R.id.btn_add_music:
105 | performAddMusic();
106 | break;
107 | case R.id.btn_mix_audio:
108 | performMixAudio();
109 | break;
110 | case R.id.btn_play_mix_audio:
111 | performPlayMixAudio();
112 | break;
113 | }
114 | }
115 |
116 | private void performAddMusic() {
117 | Intent addMusicIntent = new Intent(this, AudioChooserActivity.class);
118 | startActivityForResult(addMusicIntent, REQUEST_CODE_ADD_MUSIC);
119 | }
120 |
121 | private void performMixAudio() {
122 | if(!addAudioTracks.isEmpty()){
123 | dlgMixing.setMessage(getString(R.string.please_wait));
124 | dlgMixing.show();
125 | new MixAudioTask().execute();
126 | }
127 | }
128 |
129 | private void performPlayMixAudio(){
130 |
131 | if(mFinalMixAudioFile == null)
132 | return;
133 |
134 | File accFile = new File(mFinalMixAudioFile);
135 | if(accFile.exists()){
136 | mMisPlayService.play(Uri.fromFile(accFile));
137 | }
138 | }
139 |
140 | @Override
141 | protected void onActivityResult(int requestCode, int resultCode, Intent data) {
142 | super.onActivityResult(requestCode, resultCode, data);
143 | if (requestCode == REQUEST_CODE_ADD_MUSIC && resultCode == RESULT_OK) {
144 | dlgDecoding.show();
145 | AudioEntry audioEntry = (AudioEntry) data.getSerializableExtra("audio");
146 | new DecodeAudioTask(audioEntry).execute();
147 | }
148 | }
149 |
150 | class MixAudioTask extends AsyncTask {
151 |
152 | @Override
153 | protected Boolean doInBackground(Void... params) {
154 | int audioSize = addAudioTracks.size();
155 |
156 | boolean isSingleAudio = false;
157 | String rawAudioFile = null;
158 | if(audioSize == 1){
159 | isSingleAudio = true;
160 | rawAudioFile = addAudioTracks.iterator().next().fileUrl;
161 | }
162 |
163 | if(!isSingleAudio){
164 | File[] rawAudioFiles = new File[audioSize];
165 | StringBuilder sbMix = new StringBuilder();
166 | int index = 0;
167 | for (AudioEntry audioEntry : addAudioTracks) {
168 | rawAudioFiles[index++] = new File(audioEntry.fileUrl);
169 | sbMix.append(audioEntry.id);
170 | }
171 | final String mixFilePath = MainApplication.TEMP_AUDIO_PATH + "/"
172 | + MD5Util.getMD5Str(sbMix.toString());
173 |
174 | try {
175 | MultiAudioMixer audioMixer = MultiAudioMixer.createAudioMixer();
176 |
177 | audioMixer.setOnAudioMixListener(new OnAudioMixListener() {
178 |
179 | FileOutputStream fosRawMixAudio = new FileOutputStream(mixFilePath);
180 |
181 | @Override
182 | public void onMixing(byte[] mixBytes) throws IOException {
183 | fosRawMixAudio.write(mixBytes);
184 | }
185 |
186 | @Override
187 | public void onMixError(int errorCode) {
188 | try {
189 | if(fosRawMixAudio != null)
190 | fosRawMixAudio.close();
191 | } catch (IOException e) {
192 | e.printStackTrace();
193 | }
194 | }
195 |
196 | @Override
197 | public void onMixComplete() {
198 | try {
199 | if(fosRawMixAudio != null)
200 | fosRawMixAudio.close();
201 | } catch (IOException e) {
202 | e.printStackTrace();
203 | }
204 | }
205 |
206 | });
207 | audioMixer.mixAudios(rawAudioFiles);
208 | rawAudioFile = mixFilePath;
209 | } catch (FileNotFoundException e) {
210 | e.printStackTrace();
211 | }
212 | }
213 | AudioEncoder accEncoder = AudioEncoder.createAccEncoder(rawAudioFile);
214 | String finalMixPath = MainApplication.RECORD_AUDIO_PATH + "/MixAudioTest.acc";
215 | accEncoder.encodeToFile(finalMixPath);
216 | mFinalMixAudioFile = finalMixPath;
217 | return true;
218 | }
219 |
220 | @Override
221 | protected void onPostExecute(Boolean result) {
222 | super.onPostExecute(result);
223 | Toast.makeText(MixAudioActivity.this, "混音成功,并编码成ACC格式", Toast.LENGTH_SHORT).show();
224 | dlgMixing.cancel();
225 | }
226 | }
227 |
228 | class DecodeAudioTask extends AsyncTask {
229 |
230 | AudioEntry decAudio;
231 |
232 | DecodeAudioTask(AudioEntry decAudio) {
233 | this.decAudio = decAudio;
234 | }
235 |
236 | @Override
237 | protected Boolean doInBackground(Void... params) {
238 | String decodeFilePath = MainApplication.TEMP_AUDIO_PATH + "/"
239 | + MD5Util.getMD5Str(decAudio.fileUrl);
240 | File decodeFile = new File(decodeFilePath);
241 | if (decodeFile.exists()) {
242 | publishProgress(1.0);
243 | decAudio.fileUrl = decodeFilePath;
244 | return true;
245 | }
246 |
247 | if (decAudio.mime.contains("x-ms-wma")) {
248 | FileInputStream fisWavFile = null;
249 | FileOutputStream fosRawAudioFile = null;
250 | try {
251 | File srcAudioFile = new File(decAudio.fileUrl);
252 | long audioFileSize = srcAudioFile.length();
253 | fisWavFile = new FileInputStream(srcAudioFile);
254 | fosRawAudioFile = new FileOutputStream(decodeFile);
255 | fisWavFile.read(new byte[44]);
256 | byte[] rawBuf = new byte[1024];
257 | int readCount;
258 | double totalReadCount = 44;
259 | while ((readCount = fisWavFile.read(rawBuf)) != -1) {
260 | fosRawAudioFile.write(rawBuf, 0, readCount);
261 | totalReadCount += readCount;
262 | publishProgress(totalReadCount / audioFileSize);
263 | }
264 | publishProgress(1.0);
265 | return true;
266 | } catch (FileNotFoundException e) {
267 | e.printStackTrace();
268 | } catch (IOException e) {
269 | e.printStackTrace();
270 | } finally {
271 | try {
272 | if (fisWavFile != null)
273 | fisWavFile.close();
274 |
275 | if (fosRawAudioFile != null)
276 | fosRawAudioFile.close();
277 | } catch (IOException e) {
278 | e.printStackTrace();
279 | }
280 | }
281 | return false;
282 | } else {
283 | AudioDecoder audioDec = AudioDecoder.createDefualtDecoder(decAudio.fileUrl);
284 | try {
285 | decAudio.fileUrl = decodeFilePath;
286 | audioDec.setOnAudioDecoderListener(new OnAudioDecoderListener() {
287 | @Override
288 | public void onDecode(byte[] decodedBytes,
289 | double progress) throws IOException {
290 | publishProgress(progress);
291 | }
292 | });
293 | audioDec.decodeToFile(decodeFilePath);
294 | return true;
295 | } catch (IOException e) {
296 | e.printStackTrace();
297 | return false;
298 | }
299 | }
300 | }
301 |
302 | @Override
303 | protected void onProgressUpdate(Double... values) {
304 | super.onProgressUpdate(values);
305 | dlgDecoding.setProgress((int) (MAX_PROGRESS * values[0]));
306 | }
307 |
308 | @Override
309 | protected void onPostExecute(Boolean result) {
310 | super.onPostExecute(result);
311 | if (result) {
312 | addMisicTrack(decAudio);
313 | }
314 | dlgDecoding.cancel();
315 | }
316 |
317 | private void addMisicTrack(final AudioEntry decAudio) {
318 | if (addAudioTracks.contains(decAudio))
319 | return;
320 |
321 | addAudioTracks.add(decAudio);
322 | final View viewTrack = View.inflate(MixAudioActivity.this,
323 | R.layout.listitem_audio_info, null);
324 | TextView tvName = (TextView) viewTrack
325 | .findViewById(R.id.tv_file_name);
326 | TextView tvArtist = (TextView) viewTrack
327 | .findViewById(R.id.tv_artist);
328 | ImageView tvDel = (ImageView) viewTrack.findViewById(R.id.iv_play);
329 | tvName.setText(decAudio.fileName);
330 | tvArtist.setText(decAudio.artist);
331 | tvDel.setImageResource(R.drawable.ic_delete);
332 | tvDel.setOnClickListener(new View.OnClickListener() {
333 | @Override
334 | public void onClick(View v) {
335 | addAudioTracks.remove(decAudio);
336 | containerAudioTracks.removeView(viewTrack);
337 | }
338 | });
339 |
340 | containerAudioTracks.addView(viewTrack);
341 | }
342 | }
343 | }
344 |
--------------------------------------------------------------------------------
/src/com/musicplus/entry/AudioEntry.java:
--------------------------------------------------------------------------------
1 | package com.musicplus.entry;
2 |
3 | import java.io.Serializable;
4 |
5 | public class AudioEntry implements Serializable{
6 |
7 | private static final long serialVersionUID = 2178420052691000209L;
8 |
9 | public long id;
10 | public String fileName;
11 | public String title;
12 | public int duration;
13 | public String artist;
14 | public String album;
15 | public String year;
16 | public String mime;
17 | public String size;
18 | public String fileUrl;
19 |
20 | public boolean isPlaying;
21 |
22 | @Override
23 | public int hashCode() {
24 | return Long.valueOf(id).hashCode();
25 | }
26 |
27 | @Override
28 | public boolean equals(Object obj) {
29 | if(obj == null)
30 | return false;
31 | if(obj == this)
32 | return true;
33 | return obj instanceof AudioEntry && ((AudioEntry)obj).id == this.id;
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/com/musicplus/media/AACAudioEncoder.java:
--------------------------------------------------------------------------------
1 | package com.musicplus.media;
2 |
3 | import java.io.FileInputStream;
4 | import java.io.FileNotFoundException;
5 | import java.io.FileOutputStream;
6 | import java.io.IOException;
7 | import java.nio.ByteBuffer;
8 |
9 | import android.media.MediaCodec;
10 | import android.media.MediaCodec.BufferInfo;
11 | import android.media.MediaCodecInfo;
12 | import android.media.MediaFormat;
13 |
14 | import com.musicplus.utils.DLog;
15 |
16 | class AACAudioEncoder extends AudioEncoder {
17 |
18 | private final static String TAG = "AACAudioEncoder";
19 |
20 | private final static String AUDIO_MIME = "audio/mp4a-latm";
21 | private final static long audioBytesPerSample = 44100 * 16 / 8;
22 |
23 | AACAudioEncoder(String rawAudioFile) {
24 | super(rawAudioFile);
25 | }
26 |
27 | @Override
28 | public void encodeToFile(String outEncodeFile) {
29 | FileInputStream fisRawAudio = null;
30 | FileOutputStream fosAccAudio = null;
31 | try {
32 | fisRawAudio = new FileInputStream(rawAudioFile);
33 | fosAccAudio = new FileOutputStream(outEncodeFile);
34 |
35 | final MediaCodec audioEncoder = createACCAudioDecoder();
36 | audioEncoder.start();
37 |
38 | ByteBuffer[] audioInputBuffers = audioEncoder.getInputBuffers();
39 | ByteBuffer[] audioOutputBuffers = audioEncoder.getOutputBuffers();
40 | boolean sawInputEOS = false;
41 | boolean sawOutputEOS = false;
42 | long audioTimeUs = 0;
43 | BufferInfo outBufferInfo = new BufferInfo();
44 |
45 | boolean readRawAudioEOS = false;
46 | byte[] rawInputBytes = new byte[4096];
47 | int readRawAudioCount = 0;
48 | int rawAudioSize = 0;
49 | long lastAudioPresentationTimeUs = 0;
50 |
51 | int inputBufIndex, outputBufIndex;
52 | while (!sawOutputEOS) {
53 | if (!sawInputEOS) {
54 | inputBufIndex = audioEncoder.dequeueInputBuffer(10000);
55 | if (inputBufIndex >= 0) {
56 | ByteBuffer inputBuffer = audioInputBuffers[inputBufIndex];
57 | inputBuffer.clear();
58 |
59 | int bufferSize = inputBuffer.remaining();
60 | if (bufferSize != rawInputBytes.length) {
61 | rawInputBytes = new byte[bufferSize];
62 | }
63 |
64 | if (!readRawAudioEOS) {
65 | readRawAudioCount = fisRawAudio.read(rawInputBytes);
66 | if (readRawAudioCount == -1) {
67 | readRawAudioEOS = true;
68 | }
69 | }
70 |
71 | if (readRawAudioEOS) {
72 | audioEncoder.queueInputBuffer(inputBufIndex, 0, 0,0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
73 | sawInputEOS = true;
74 | } else {
75 | inputBuffer.put(rawInputBytes, 0, readRawAudioCount);
76 | rawAudioSize += readRawAudioCount;
77 | audioEncoder.queueInputBuffer(inputBufIndex, 0,readRawAudioCount, audioTimeUs, 0);
78 | audioTimeUs = (long) (1000000 * (rawAudioSize / 2.0) / audioBytesPerSample);
79 | }
80 | }
81 | }
82 |
83 | outputBufIndex = audioEncoder.dequeueOutputBuffer(outBufferInfo, 10000);
84 | if (outputBufIndex >= 0) {
85 |
86 | // Simply ignore codec config buffers.
87 | if ((outBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
88 | DLog.i(TAG, "audio encoder: codec config buffer");
89 | audioEncoder.releaseOutputBuffer(outputBufIndex, false);
90 | continue;
91 | }
92 |
93 | if (outBufferInfo.size != 0) {
94 | ByteBuffer outBuffer = audioOutputBuffers[outputBufIndex];
95 | outBuffer.position(outBufferInfo.offset);
96 | outBuffer.limit(outBufferInfo.offset+ outBufferInfo.size);
97 | DLog.i(TAG,
98 | String.format(
99 | " writing audio sample : size=%s , presentationTimeUs=%s",
100 | outBufferInfo.size,
101 | outBufferInfo.presentationTimeUs));
102 | if (lastAudioPresentationTimeUs < outBufferInfo.presentationTimeUs) {
103 | lastAudioPresentationTimeUs = outBufferInfo.presentationTimeUs;
104 | int outBufSize = outBufferInfo.size;
105 | int outPacketSize = outBufSize + 7;
106 |
107 | outBuffer.position(outBufferInfo.offset);
108 | outBuffer.limit(outBufferInfo.offset + outBufSize);
109 |
110 | byte[] outData = new byte[outBufSize + 7];
111 | addADTStoPacket(outData, outPacketSize);
112 | outBuffer.get(outData, 7, outBufSize);
113 |
114 | fosAccAudio.write(outData, 0, outData.length);
115 | DLog.i(TAG, outData.length + " bytes written.");
116 | } else {
117 | DLog.e(TAG,
118 | "error sample! its presentationTimeUs should not lower than before.");
119 | }
120 | }
121 |
122 | audioEncoder.releaseOutputBuffer(outputBufIndex, false);
123 |
124 | if ((outBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
125 | sawOutputEOS = true;
126 | }
127 | } else if (outputBufIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
128 | audioOutputBuffers = audioEncoder.getOutputBuffers();
129 | } else if (outputBufIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
130 | MediaFormat audioFormat = audioEncoder.getOutputFormat();
131 | DLog.i(TAG, "format change : " + audioFormat);
132 | }
133 | }
134 |
135 | DLog.i(TAG,"acc encode done");
136 | } catch (FileNotFoundException e) {
137 | e.printStackTrace();
138 | } catch (IOException e) {
139 | e.printStackTrace();
140 | } finally {
141 |
142 | try {
143 | if (fisRawAudio != null)
144 | fisRawAudio.close();
145 | if (fosAccAudio != null)
146 | fosAccAudio.close();
147 | } catch (IOException e) {
148 | e.printStackTrace();
149 | }
150 | }
151 | }
152 |
153 | private MediaCodec createACCAudioDecoder() throws IOException {
154 | MediaCodec codec = MediaCodec.createEncoderByType(AUDIO_MIME);
155 | MediaFormat format = new MediaFormat();
156 | format.setString(MediaFormat.KEY_MIME, AUDIO_MIME);
157 | format.setInteger(MediaFormat.KEY_BIT_RATE, 128000);
158 | format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 2);
159 | format.setInteger(MediaFormat.KEY_SAMPLE_RATE, 44100);
160 | format.setInteger(MediaFormat.KEY_AAC_PROFILE,
161 | MediaCodecInfo.CodecProfileLevel.AACObjectLC);
162 | codec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
163 | return codec;
164 | }
165 |
166 | /**
167 | * Add ADTS header at the beginning of each and every AAC packet. This is
168 | * needed as MediaCodec encoder generates a packet of raw AAC data.
169 | *
170 | * Note the packetLen must count in the ADTS header itself.
171 | **/
172 | private void addADTStoPacket(byte[] packet, int packetLen) {
173 | int profile = 2; // AAC LC
174 | int freqIdx = 4; // 44.1KHz
175 | int chanCfg = 2; // Channel Configurations
176 |
177 | // fill in ADTS data
178 | packet[0] = (byte) 0xFF;
179 | packet[1] = (byte) 0xF9;
180 | packet[2] = (byte) (((profile - 1) << 6) + (freqIdx << 2) + (chanCfg >> 2));
181 | packet[3] = (byte) (((chanCfg & 3) << 6) + (packetLen >> 11));
182 | packet[4] = (byte) ((packetLen & 0x7FF) >> 3);
183 | packet[5] = (byte) (((packetLen & 7) << 5) + 0x1F);
184 | packet[6] = (byte) 0xFC;
185 | }
186 | }
187 |
--------------------------------------------------------------------------------
/src/com/musicplus/media/AndroidAudioDecoder.java:
--------------------------------------------------------------------------------
1 | package com.musicplus.media;
2 |
3 | import java.io.FileOutputStream;
4 | import java.io.IOException;
5 | import java.nio.ByteBuffer;
6 |
7 | import android.media.MediaCodec;
8 | import android.media.MediaExtractor;
9 | import android.media.MediaFormat;
10 |
11 | import com.musicplus.utils.DLog;
12 |
13 | /**
14 | * Android SDK 支持的编码器
15 | * @author Darcy
16 | * @version android.os.Build.VERSION.SDK_INT >= 16
17 | */
18 | public class AndroidAudioDecoder extends AudioDecoder{
19 |
20 | private static final String TAG = "AndroidAudioDecoder";
21 |
22 | AndroidAudioDecoder(String encodefile) {
23 | super(encodefile);
24 | }
25 |
26 | @Override
27 | public RawAudioInfo decodeToFile(String outFile) throws IOException{
28 |
29 | long beginTime = System.currentTimeMillis();
30 |
31 | final String encodeFile = mEncodeFile;
32 | MediaExtractor extractor = new MediaExtractor();
33 | extractor.setDataSource(encodeFile);
34 |
35 | MediaFormat mediaFormat = null;
36 | for (int i = 0; i < extractor.getTrackCount(); i++) {
37 | MediaFormat format = extractor.getTrackFormat(i);
38 | String mime = format.getString(MediaFormat.KEY_MIME);
39 | if (mime.startsWith("audio/")) {
40 | extractor.selectTrack(i);
41 | mediaFormat = format;
42 | break;
43 | }
44 | }
45 |
46 | if(mediaFormat == null){
47 | DLog.e("not a valid file with audio track..");
48 | extractor.release();
49 | return null;
50 | }
51 |
52 | RawAudioInfo rawAudioInfo = new RawAudioInfo();
53 | rawAudioInfo.channel = mediaFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
54 | rawAudioInfo.sampleRate = mediaFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE);
55 | rawAudioInfo.tempRawFile = outFile;
56 | FileOutputStream fosDecoder = new FileOutputStream(rawAudioInfo.tempRawFile);
57 |
58 | String mediaMime = mediaFormat.getString(MediaFormat.KEY_MIME);
59 | MediaCodec codec = MediaCodec.createDecoderByType(mediaMime);
60 | codec.configure(mediaFormat, null, null, 0);
61 | codec.start();
62 |
63 | ByteBuffer[] codecInputBuffers = codec.getInputBuffers();
64 | ByteBuffer[] codecOutputBuffers = codec.getOutputBuffers();
65 |
66 | final double audioDurationUs = mediaFormat.getLong(MediaFormat.KEY_DURATION);
67 | final long kTimeOutUs = 5000;
68 | MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
69 | boolean sawInputEOS = false;
70 | boolean sawOutputEOS = false;
71 | int totalRawSize = 0;
72 | try{
73 | while (!sawOutputEOS) {
74 | if (!sawInputEOS) {
75 | int inputBufIndex = codec.dequeueInputBuffer(kTimeOutUs);
76 | if (inputBufIndex >= 0) {
77 | ByteBuffer dstBuf = codecInputBuffers[inputBufIndex];
78 | int sampleSize = extractor.readSampleData(dstBuf, 0);
79 | if (sampleSize < 0) {
80 | DLog.i(TAG, "saw input EOS.");
81 | sawInputEOS = true;
82 | codec.queueInputBuffer(inputBufIndex,0,0,0,MediaCodec.BUFFER_FLAG_END_OF_STREAM );
83 | } else {
84 | long presentationTimeUs = extractor.getSampleTime();
85 | codec.queueInputBuffer(inputBufIndex,0,sampleSize,presentationTimeUs,0);
86 | extractor.advance();
87 | }
88 | }
89 | }
90 | int res = codec.dequeueOutputBuffer(info, kTimeOutUs);
91 | if (res >= 0) {
92 |
93 | int outputBufIndex = res;
94 | // Simply ignore codec config buffers.
95 | if ((info.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG)!= 0) {
96 | DLog.i(TAG, "audio encoder: codec config buffer");
97 | codec.releaseOutputBuffer(outputBufIndex, false);
98 | continue;
99 | }
100 |
101 | if(info.size != 0){
102 |
103 | ByteBuffer outBuf = codecOutputBuffers[outputBufIndex];
104 |
105 | outBuf.position(info.offset);
106 | outBuf.limit(info.offset + info.size);
107 | byte[] data = new byte[info.size];
108 | outBuf.get(data);
109 | totalRawSize += data.length;
110 | fosDecoder.write(data);
111 | if(mOnAudioDecoderListener != null)
112 | mOnAudioDecoderListener.onDecode(data, info.presentationTimeUs / audioDurationUs);
113 | DLog.i(TAG, mEncodeFile + " presentationTimeUs : " + info.presentationTimeUs);
114 | }
115 |
116 | codec.releaseOutputBuffer(outputBufIndex, false);
117 |
118 | if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
119 | DLog.i(TAG, "saw output EOS.");
120 | sawOutputEOS = true;
121 | }
122 |
123 | } else if (res == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
124 | codecOutputBuffers = codec.getOutputBuffers();
125 | DLog.i(TAG, "output buffers have changed.");
126 | } else if (res == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
127 | MediaFormat oformat = codec.getOutputFormat();
128 | DLog.i(TAG, "output format has changed to " + oformat);
129 | }
130 | }
131 | rawAudioInfo.size = totalRawSize;
132 |
133 | if(mOnAudioDecoderListener != null)
134 | mOnAudioDecoderListener.onDecode(null, 1);
135 |
136 | DLog.i(TAG, "decode "+outFile+" cost " + (System.currentTimeMillis() - beginTime) +" milliseconds !");
137 |
138 | return rawAudioInfo;
139 | }finally{
140 | fosDecoder.close();
141 | codec.stop();
142 | codec.release();
143 | extractor.release();
144 | }
145 |
146 | }
147 | }
148 |
--------------------------------------------------------------------------------
/src/com/musicplus/media/AudioDecoder.java:
--------------------------------------------------------------------------------
1 | package com.musicplus.media;
2 |
3 | import java.io.IOException;
4 |
5 | /**
6 | * 音频解码器
7 | * @author Darcy
8 | *
9 | */
10 | public abstract class AudioDecoder {
11 |
12 | String mEncodeFile;
13 |
14 | OnAudioDecoderListener mOnAudioDecoderListener;
15 |
16 | AudioDecoder(String encodefile){
17 | this.mEncodeFile = encodefile;
18 | }
19 |
20 | public static AudioDecoder createDefualtDecoder(String encodefile){
21 | return new AndroidAudioDecoder(encodefile);
22 | }
23 |
24 | public void setOnAudioDecoderListener(OnAudioDecoderListener l ){
25 | this.mOnAudioDecoderListener = l;
26 | }
27 |
28 | /**
29 | * 解码
30 | * @return
31 | * @throws IOException
32 | */
33 | public abstract RawAudioInfo decodeToFile(String outFile) throws IOException;
34 |
35 | public static class RawAudioInfo{
36 | public String tempRawFile;
37 | public int size;
38 | public long sampleRate;
39 | public int channel;
40 | }
41 |
42 | public interface OnAudioDecoderListener{
43 | /**
44 | * monitor when processing decode
45 | * @param decodedBytes
46 | * @param progress range 0~1
47 | * @throws IOException
48 | */
49 | void onDecode(byte[] decodedBytes, double progress) throws IOException;
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/com/musicplus/media/AudioEncoder.java:
--------------------------------------------------------------------------------
1 | package com.musicplus.media;
2 |
3 |
4 | public abstract class AudioEncoder {
5 |
6 | String rawAudioFile;
7 |
8 | AudioEncoder(String rawAudioFile){
9 | this.rawAudioFile = rawAudioFile;
10 | }
11 |
12 | public static AudioEncoder createAccEncoder(String rawAudioFile){
13 | return new AACAudioEncoder(rawAudioFile);
14 | }
15 |
16 | public abstract void encodeToFile(String outEncodeFile);
17 | }
18 |
--------------------------------------------------------------------------------
/src/com/musicplus/media/CameraHelper.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2013 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.musicplus.media;
18 |
19 | import android.annotation.TargetApi;
20 | import android.hardware.Camera;
21 | import android.os.Build;
22 | import android.os.Environment;
23 | import android.util.Log;
24 |
25 | import java.io.File;
26 | import java.text.SimpleDateFormat;
27 | import java.util.Date;
28 | import java.util.List;
29 |
30 | /**
31 | * Camera related utilities.
32 | */
33 | @Deprecated
34 | public class CameraHelper {
35 |
36 | public static final int MEDIA_TYPE_IMAGE = 1;
37 | public static final int MEDIA_TYPE_VIDEO = 2;
38 |
39 | /**
40 | * Iterate over supported camera preview sizes to see which one best fits the
41 | * dimensions of the given view while maintaining the aspect ratio. If none can,
42 | * be lenient with the aspect ratio.
43 | *
44 | * @param sizes Supported camera preview sizes.
45 | * @param w The width of the view.
46 | * @param h The height of the view.
47 | * @return Best match camera preview size to fit in the view.
48 | */
49 | public static Camera.Size getOptimalPreviewSize(List sizes, int w, int h) {
50 | // Use a very small tolerance because we want an exact match.
51 | final double ASPECT_TOLERANCE = 0.1;
52 | double targetRatio = (double) w / h;
53 | if (sizes == null)
54 | return null;
55 |
56 | Camera.Size optimalSize = null;
57 |
58 | // Start with max value and refine as we iterate over available preview sizes. This is the
59 | // minimum difference between view and camera height.
60 | double minDiff = Double.MAX_VALUE;
61 |
62 | // Target view height
63 | int targetHeight = h;
64 |
65 | // Try to find a preview size that matches aspect ratio and the target view size.
66 | // Iterate over all available sizes and pick the largest size that can fit in the view and
67 | // still maintain the aspect ratio.
68 | for (Camera.Size size : sizes) {
69 | double ratio = (double) size.width / size.height;
70 | if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE)
71 | continue;
72 | if (Math.abs(size.height - targetHeight) < minDiff) {
73 | optimalSize = size;
74 | minDiff = Math.abs(size.height - targetHeight);
75 | }
76 | }
77 |
78 | // Cannot find preview size that matches the aspect ratio, ignore the requirement
79 | if (optimalSize == null) {
80 | minDiff = Double.MAX_VALUE;
81 | for (Camera.Size size : sizes) {
82 | if (Math.abs(size.height - targetHeight) < minDiff) {
83 | optimalSize = size;
84 | minDiff = Math.abs(size.height - targetHeight);
85 | }
86 | }
87 | }
88 | return optimalSize;
89 | }
90 |
91 | /**
92 | * @return the default camera on the device. Return null if there is no camera on the device.
93 | */
94 | public static Camera getDefaultCameraInstance() {
95 | return Camera.open();
96 | }
97 |
98 |
99 | /**
100 | * @return the default rear/back facing camera on the device. Returns null if camera is not
101 | * available.
102 | */
103 | public static Camera getDefaultBackFacingCameraInstance() {
104 | return getDefaultCamera(Camera.CameraInfo.CAMERA_FACING_BACK);
105 | }
106 |
107 | /**
108 | * @return the default front facing camera on the device. Returns null if camera is not
109 | * available.
110 | */
111 | public static Camera getDefaultFrontFacingCameraInstance() {
112 | return getDefaultCamera(Camera.CameraInfo.CAMERA_FACING_FRONT);
113 | }
114 |
115 |
116 | /**
117 | *
118 | * @param position Physical position of the camera i.e Camera.CameraInfo.CAMERA_FACING_FRONT
119 | * or Camera.CameraInfo.CAMERA_FACING_BACK.
120 | * @return the default camera on the device. Returns null if camera is not available.
121 | */
122 | @TargetApi(Build.VERSION_CODES.GINGERBREAD)
123 | private static Camera getDefaultCamera(int position) {
124 | // Find the total number of cameras available
125 | int mNumberOfCameras = Camera.getNumberOfCameras();
126 |
127 | // Find the ID of the back-facing ("default") camera
128 | Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
129 | for (int i = 0; i < mNumberOfCameras; i++) {
130 | Camera.getCameraInfo(i, cameraInfo);
131 | if (cameraInfo.facing == position) {
132 | return Camera.open(i);
133 |
134 | }
135 | }
136 |
137 | return null;
138 | }
139 |
140 | /**
141 | * Creates a media file in the {@code Environment.DIRECTORY_PICTURES} directory. The directory
142 | * is persistent and available to other applications like gallery.
143 | *
144 | * @param type Media type. Can be video or image.
145 | * @return A file object pointing to the newly created file.
146 | */
147 | public static File getOutputMediaFile(int type){
148 | // To be safe, you should check that the SDCard is mounted
149 | // using Environment.getExternalStorageState() before doing this.
150 | if (!Environment.getExternalStorageState().equalsIgnoreCase(Environment.MEDIA_MOUNTED)) {
151 | return null;
152 | }
153 |
154 | File mediaStorageDir = new File(Environment.getExternalStoragePublicDirectory(
155 | Environment.DIRECTORY_PICTURES), "CameraSample");
156 | // This location works best if you want the created images to be shared
157 | // between applications and persist after your app has been uninstalled.
158 |
159 | // Create the storage directory if it does not exist
160 | if (! mediaStorageDir.exists()){
161 | if (! mediaStorageDir.mkdirs()) {
162 | Log.d("CameraSample", "failed to create directory");
163 | return null;
164 | }
165 | }
166 |
167 | // Create a media file name
168 | String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
169 | File mediaFile;
170 | if (type == MEDIA_TYPE_IMAGE){
171 | mediaFile = new File(mediaStorageDir.getPath() + File.separator +
172 | "IMG_"+ timeStamp + ".jpg");
173 | } else if(type == MEDIA_TYPE_VIDEO) {
174 | mediaFile = new File(mediaStorageDir.getPath() + File.separator +
175 | "VID_"+ timeStamp + ".mp4");
176 | } else {
177 | return null;
178 | }
179 |
180 | return mediaFile;
181 | }
182 |
183 | }
184 |
--------------------------------------------------------------------------------
/src/com/musicplus/media/MediaConstants.java:
--------------------------------------------------------------------------------
1 | package com.musicplus.media;
2 |
3 | public interface MediaConstants {
4 | int AUDIO_CHANNEL = 2;
5 | int AUDIO_SAMPLE_RATE = 44100;
6 |
7 | int VIDEO_FRAME_RATE = 25;
8 | }
9 |
--------------------------------------------------------------------------------
/src/com/musicplus/media/MediaUtils.java:
--------------------------------------------------------------------------------
1 | package com.musicplus.media;
2 |
3 | import java.io.IOException;
4 |
5 | import android.media.MediaExtractor;
6 | import android.media.MediaFormat;
7 |
8 | public class MediaUtils {
9 |
10 | /**
11 | * 该音频是否符合采样率是sampleRate,通道数是channelCount,值为-1表示忽略该条件
12 | *
13 | * @param audioFile
14 | * @param sampleRate
15 | * @param channelCount
16 | * @return
17 | */
18 | public static boolean isMatchAudioFormat(String audioFile, int sampleRate,int channelCount){
19 | MediaExtractor mex = new MediaExtractor();
20 | try {
21 | mex.setDataSource(audioFile);
22 | } catch (IOException e) {
23 | e.printStackTrace();
24 | }
25 |
26 | MediaFormat mf = mex.getTrackFormat(0);
27 |
28 | boolean result = true;
29 | if(sampleRate != -1){
30 | result = sampleRate == mf.getInteger(MediaFormat.KEY_SAMPLE_RATE);
31 | }
32 |
33 | if(result && channelCount != -1){
34 | result = channelCount == mf.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
35 | }
36 |
37 | mex.release();
38 |
39 | return result;
40 | }
41 |
42 | /**
43 | * 生成Wav文件的头部信息
44 | *
45 | * @param rawAudioSize
46 | * @param sampleRate
47 | * @param channels
48 | *
49 | */
50 | public final static byte[] createWaveFileHeader(long rawAudioSize,int channels, long sampleRate, int bitsPerSample){
51 |
52 | final byte[] header = new byte[44];
53 |
54 | //ChunkID : "RIFF"
55 | header[0] = 'R';
56 | header[1] = 'I';
57 | header[2] = 'F';
58 | header[3] = 'F';
59 |
60 | //ChunkSize : ChunkSize = 36 + SubChunk2Size
61 | long totalDataLen = 36 + rawAudioSize;
62 | header[4] = (byte) (totalDataLen & 0xff);
63 | header[5] = (byte) ((totalDataLen >> 8) & 0xff);
64 | header[6] = (byte) ((totalDataLen >> 16) & 0xff);
65 | header[7] = (byte) ((totalDataLen >> 24) & 0xff);
66 |
67 | //Format
68 | header[8] = 'W';
69 | header[9] = 'A';
70 | header[10] = 'V';
71 | header[11] = 'E';
72 |
73 | //Subchunk1ID : "fmt"
74 | header[12] = 'f';
75 | header[13] = 'm';
76 | header[14] = 't';
77 | header[15] = ' ';
78 |
79 | //Subchunk1Size : 16 for PCM
80 | header[16] = 16;
81 | header[17] = 0;
82 | header[18] = 0;
83 | header[19] = 0;
84 |
85 | //AudioFormat : PCM = 1
86 | header[20] = 1;
87 | header[21] = 0;
88 |
89 | //NumChannels : Mono = 1, Stereo = 2
90 | header[22] = (byte) channels;
91 | header[23] = 0;
92 |
93 | //SampleRate
94 | header[24] = (byte) (sampleRate & 0xff);
95 | header[25] = (byte) ((sampleRate >> 8) & 0xff);
96 | header[26] = (byte) ((sampleRate >> 16) & 0xff);
97 | header[27] = (byte) ((sampleRate >> 24) & 0xff);
98 |
99 | //ByteRate : ByteRate = SampleRate * NumChannels * BitsPerSample/8
100 | long byteRate = sampleRate * channels * bitsPerSample / 8;
101 | header[28] = (byte) (byteRate & 0xff);
102 | header[29] = (byte) ((byteRate >> 8) & 0xff);
103 | header[30] = (byte) ((byteRate >> 16) & 0xff);
104 | header[31] = (byte) ((byteRate >> 24) & 0xff);
105 |
106 | //BlockAlign : BlockAlign = NumChannels * BitsPerSample/8
107 | header[32] = (byte) (2 * 16 / 8);
108 | header[33] = 0;
109 |
110 | //BitsPerSample : 8 bits = 8, 16 bits = 16, etc
111 | header[34] = (byte) bitsPerSample;
112 | header[35] = 0;
113 |
114 | //Subchunk2ID : "data"
115 | header[36] = 'd';
116 | header[37] = 'a';
117 | header[38] = 't';
118 | header[39] = 'a';
119 |
120 | //Subchunk2Size : Subchunk2Size = NumSamples * NumChannels * BitsPerSample/8
121 | header[40] = (byte) (rawAudioSize & 0xff);
122 | header[41] = (byte) ((rawAudioSize >> 8) & 0xff);
123 | header[42] = (byte) ((rawAudioSize >> 16) & 0xff);
124 | header[43] = (byte) ((rawAudioSize >> 24) & 0xff);
125 |
126 | return header;
127 | }
128 | }
129 |
--------------------------------------------------------------------------------
/src/com/musicplus/media/MultiAudioMixer.java:
--------------------------------------------------------------------------------
1 | package com.musicplus.media;
2 |
3 | import java.io.File;
4 | import java.io.FileInputStream;
5 | import java.io.IOException;
6 | import java.util.Arrays;
7 |
8 | import android.util.Log;
9 |
10 | /**
11 | * 目前只能对相同采样率,通道和采样精度的音频进行混音
12 | * @author Darcy
13 | */
14 | public abstract class MultiAudioMixer {
15 |
16 | private OnAudioMixListener mOnAudioMixListener;
17 |
18 | public static MultiAudioMixer createAudioMixer(){
19 | return new AverageAudioMixer();
20 | }
21 |
22 | public void setOnAudioMixListener(OnAudioMixListener l){
23 | this.mOnAudioMixListener = l;
24 | }
25 |
26 | /**
27 | * start to mix , you can call {@link #setOnAudioMixListener(OnAudioMixListener)} before this method to get mixed data.
28 | */
29 | public void mixAudios(File[] rawAudioFiles){
30 |
31 | final int fileSize = rawAudioFiles.length;
32 |
33 | FileInputStream[] audioFileStreams = new FileInputStream[fileSize];
34 | File audioFile = null;
35 |
36 | FileInputStream inputStream;
37 | byte[][] allAudioBytes = new byte[fileSize][];
38 | boolean[] streamDoneArray = new boolean[fileSize];
39 | byte[] buffer = new byte[512];
40 | int offset;
41 |
42 | try {
43 |
44 | for (int fileIndex = 0; fileIndex < fileSize; ++fileIndex) {
45 | audioFile = rawAudioFiles[fileIndex];
46 | audioFileStreams[fileIndex] = new FileInputStream(audioFile);
47 | }
48 |
49 | while(true){
50 |
51 | for(int streamIndex = 0 ; streamIndex < fileSize ; ++streamIndex){
52 |
53 | inputStream = audioFileStreams[streamIndex];
54 | if(!streamDoneArray[streamIndex] && (offset = inputStream.read(buffer)) != -1){
55 | allAudioBytes[streamIndex] = Arrays.copyOf(buffer,buffer.length);
56 | }else{
57 | streamDoneArray[streamIndex] = true;
58 | allAudioBytes[streamIndex] = new byte[512];
59 | }
60 | }
61 |
62 | byte[] mixBytes = mixRawAudioBytes(allAudioBytes);
63 | if(mixBytes != null && mOnAudioMixListener != null){
64 | mOnAudioMixListener.onMixing(mixBytes);
65 | }
66 |
67 | boolean done = true;
68 | for(boolean streamEnd : streamDoneArray){
69 | if(!streamEnd){
70 | done = false;
71 | }
72 | }
73 |
74 | if(done){
75 | if(mOnAudioMixListener != null)
76 | mOnAudioMixListener.onMixComplete();
77 | break;
78 | }
79 | }
80 |
81 | } catch (IOException e) {
82 | e.printStackTrace();
83 | if(mOnAudioMixListener != null)
84 | mOnAudioMixListener.onMixError(1);
85 | }finally{
86 | try {
87 | for(FileInputStream in : audioFileStreams){
88 | if(in != null)
89 | in.close();
90 | }
91 | } catch (IOException e) {
92 | e.printStackTrace();
93 | }
94 | }
95 | }
96 |
97 | abstract byte[] mixRawAudioBytes(byte[][] data);
98 |
99 | public interface OnAudioMixListener{
100 | /**
101 | * invoke when mixing, if you want to stop the mixing process, you can throw an AudioMixException
102 | * @param mixBytes
103 | * @throws AudioMixException
104 | */
105 | void onMixing(byte[] mixBytes) throws IOException;
106 |
107 | void onMixError(int errorCode);
108 |
109 | /**
110 | * invoke when mix success
111 | */
112 | void onMixComplete();
113 | }
114 |
115 | public static class AudioMixException extends IOException{
116 | private static final long serialVersionUID = -1344782236320621800L;
117 |
118 | public AudioMixException(String msg){
119 | super(msg);
120 | }
121 | }
122 |
123 | /**
124 | * 平均值算法
125 | * @author Darcy
126 | */
127 | private static class AverageAudioMixer extends MultiAudioMixer{
128 |
129 | @Override
130 | byte[] mixRawAudioBytes(byte[][] bMulRoadAudioes) {
131 |
132 | if (bMulRoadAudioes == null || bMulRoadAudioes.length == 0)
133 | return null;
134 |
135 | byte[] realMixAudio = bMulRoadAudioes[0];
136 |
137 | if(bMulRoadAudioes.length == 1)
138 | return realMixAudio;
139 |
140 | for(int rw = 0 ; rw < bMulRoadAudioes.length ; ++rw){
141 | if(bMulRoadAudioes[rw].length != realMixAudio.length){
142 | Log.e("app", "column of the road of audio + " + rw +" is diffrent.");
143 | return null;
144 | }
145 | }
146 |
147 | int row = bMulRoadAudioes.length;
148 | int coloum = realMixAudio.length / 2;
149 | short[][] sMulRoadAudioes = new short[row][coloum];
150 |
151 | for (int r = 0; r < row; ++r) {
152 | for (int c = 0; c < coloum; ++c) {
153 | sMulRoadAudioes[r][c] = (short) ((bMulRoadAudioes[r][c * 2] & 0xff) | (bMulRoadAudioes[r][c * 2 + 1] & 0xff) << 8);
154 | }
155 | }
156 |
157 | short[] sMixAudio = new short[coloum];
158 | int mixVal;
159 | int sr = 0;
160 | for (int sc = 0; sc < coloum; ++sc) {
161 | mixVal = 0;
162 | sr = 0;
163 | for (; sr < row; ++sr) {
164 | mixVal += sMulRoadAudioes[sr][sc];
165 | }
166 | sMixAudio[sc] = (short) (mixVal / row);
167 | }
168 |
169 | for (sr = 0; sr < coloum; ++sr) {
170 | realMixAudio[sr * 2] = (byte) (sMixAudio[sr] & 0x00FF);
171 | realMixAudio[sr * 2 + 1] = (byte) ((sMixAudio[sr] & 0xFF00) >> 8);
172 | }
173 |
174 | return realMixAudio;
175 | }
176 |
177 | }
178 | }
179 |
--------------------------------------------------------------------------------
/src/com/musicplus/media/MultiRawAudioPlayer.java:
--------------------------------------------------------------------------------
1 | package com.musicplus.media;
2 |
3 | import java.io.File;
4 | import java.io.FileInputStream;
5 | import java.io.FileNotFoundException;
6 | import java.io.FileOutputStream;
7 | import java.io.IOException;
8 | import java.util.concurrent.BrokenBarrierException;
9 | import java.util.concurrent.CyclicBarrier;
10 |
11 | import android.media.AudioFormat;
12 | import android.media.AudioManager;
13 | import android.media.AudioTrack;
14 | import android.os.Handler;
15 | import android.os.Message;
16 |
17 | import com.musicplus.app.MainApplication;
18 | import com.musicplus.entry.AudioEntry;
19 | import com.musicplus.media.MultiAudioMixer.AudioMixException;
20 | import com.musicplus.media.MultiAudioMixer.OnAudioMixListener;
21 | import com.musicplus.utils.MD5Util;
22 |
23 | /**
24 | * 多路音频同时播放器,原理是通过{@link MultiAudioMixer}进行混音成一条音轨进行播放
25 | * @author Darcy
26 | */
27 | public class MultiRawAudioPlayer {
28 |
29 | private final static int sampleRateInHz = 44100;
30 | private final static int channelConfig = AudioFormat.CHANNEL_OUT_STEREO;
31 | private final static int audioFormat = AudioFormat.ENCODING_PCM_16BIT;
32 |
33 | private volatile boolean playing = false;
34 | private volatile boolean stopped = false;
35 | private final Object mLock = new Object();
36 |
37 | private static final int MSG_PLAY_START = 0x1;
38 | private static final int MSG_PLAY_STOP = 0x2;
39 | private static final int MSG_PLAY_COMPLETE = 0x3;
40 |
41 | private EventHandler mEventHandler = new EventHandler();
42 | private CyclicBarrier mRecordBarrier; //同步录像
43 |
44 | private AudioEntry[] mAudioEntries;
45 |
46 | private static class EventHandler extends Handler{
47 |
48 | private OnRawAudioPlayListener event;
49 |
50 | public void setOnRawAudioPlayListener(OnRawAudioPlayListener l){
51 | event = l;
52 | }
53 |
54 | public void handleMessage(android.os.Message msg) {
55 | String tempMixFile = (String) msg.obj;
56 | switch(msg.what){
57 | case MSG_PLAY_START:
58 | if(event != null)
59 | event.onPlayStart();
60 | break;
61 | case MSG_PLAY_STOP:
62 | if(event != null)
63 | event.onPlayStop(tempMixFile);
64 | break;
65 | case MSG_PLAY_COMPLETE:
66 | if(event != null)
67 | event.onPlayComplete(tempMixFile);
68 | break;
69 | }
70 | };
71 | }
72 |
73 | public MultiRawAudioPlayer(AudioEntry[] audioEntries,CyclicBarrier recordBarrier){
74 | this.mAudioEntries = audioEntries;
75 | this.mRecordBarrier = recordBarrier;
76 | }
77 |
78 | public void setOnRawAudioPlayListener(OnRawAudioPlayListener l){
79 | mEventHandler.setOnRawAudioPlayListener(l);
80 | }
81 |
82 | /**
83 | * 调用该方法开始播放或者从头播放
84 | */
85 | public void play(){
86 |
87 | if(mAudioEntries == null || mAudioEntries.length == 0)
88 | return;
89 |
90 | if(playing){
91 | stop();
92 | play();
93 | }else{
94 | stopped = false;
95 | playing = true;
96 | new PlayThread().start();
97 | }
98 | }
99 |
100 | public void testPlaySync(){
101 | new PlayThread().run();
102 | }
103 |
104 | /**
105 | * 停止播放
106 | */
107 | public void stop(){
108 | stopped = true;
109 | if(playing){
110 | synchronized (mLock) {
111 | try {
112 | mLock.wait();
113 | } catch (InterruptedException e) {
114 | e.printStackTrace();
115 | }
116 | }
117 | }
118 | }
119 |
120 | AudioTrack createTrack(){
121 | int bufferSizeInBytes = AudioTrack.getMinBufferSize(sampleRateInHz, channelConfig, audioFormat);
122 | return new AudioTrack(AudioManager.STREAM_MUSIC, sampleRateInHz, channelConfig, audioFormat, bufferSizeInBytes, AudioTrack.MODE_STREAM);
123 | }
124 |
125 | private void waitForRecordStart(){
126 | if(mRecordBarrier != null){
127 | try {
128 | mRecordBarrier.await();
129 | } catch (BrokenBarrierException e) {
130 | e.printStackTrace();
131 | } catch (InterruptedException e) {
132 | e.printStackTrace();
133 | }
134 | }
135 | }
136 |
137 | private class PlayThread extends Thread{
138 |
139 | @Override
140 | public void run() {
141 | int audioSize = mAudioEntries.length;
142 | File[] rawAudioFiles = new File[audioSize];
143 | StringBuilder sbMix = new StringBuilder();
144 | for(int index = 0; index < audioSize ; ++index){
145 | AudioEntry audioEntry = mAudioEntries[index];
146 | rawAudioFiles[index] = new File(audioEntry.fileUrl);
147 | sbMix.append(audioEntry.id);
148 | }
149 |
150 | boolean hasMixBefore = false;
151 | final String mixFilePath = MainApplication.TEMP_AUDIO_PATH + "/" +MD5Util.getMD5Str(sbMix.toString());
152 | final File tempMixFile = new File(mixFilePath);
153 |
154 | final AudioTrack audioTrack = createTrack();
155 | audioTrack.play();
156 |
157 | try{
158 | final FileOutputStream fosTempMixAudio = new FileOutputStream(tempMixFile);
159 | //single audio
160 | if(audioSize == 1 || hasMixBefore){
161 | FileInputStream audioInput = null;
162 | try {
163 | File singleAudioFile = hasMixBefore ? tempMixFile : rawAudioFiles[0];
164 | audioInput = new FileInputStream(singleAudioFile);
165 | byte[] audioData = new byte[512];
166 | int readCount=0, totalSize=0;
167 |
168 | if(!stopped && (readCount = audioInput.read(audioData))!= -1){
169 | fosTempMixAudio.write(audioData, 0, readCount);
170 | mEventHandler.sendEmptyMessage(MSG_PLAY_START);
171 | waitForRecordStart();
172 | audioTrack.write(audioData, 0, readCount);
173 | totalSize += readCount;
174 | }
175 |
176 | while(!stopped && (readCount = audioInput.read(audioData))!= -1){
177 | fosTempMixAudio.write(audioData, 0, readCount);
178 | audioTrack.write(audioData, 0, readCount);
179 | totalSize += readCount;
180 | }
181 |
182 | if(totalSize >= singleAudioFile.length()){
183 | Message completeMsg = mEventHandler.obtainMessage(MSG_PLAY_COMPLETE, mixFilePath);
184 | mEventHandler.sendMessage(completeMsg);
185 | }
186 |
187 | if(stopped){
188 | Message stopMsg = mEventHandler.obtainMessage(MSG_PLAY_STOP, mixFilePath);
189 | mEventHandler.sendMessage(stopMsg);
190 | }
191 | } catch (FileNotFoundException e) {
192 | e.printStackTrace();
193 | } catch (IOException e) {
194 | e.printStackTrace();
195 | }finally{
196 | try {
197 | if(audioInput != null)
198 | audioInput.close();
199 |
200 | if(fosTempMixAudio != null)
201 | fosTempMixAudio.close();
202 | } catch (IOException e) {
203 | e.printStackTrace();
204 | }
205 | }
206 | return;
207 | }
208 |
209 | // multi audios
210 | MultiAudioMixer mixer = MultiAudioMixer.createAudioMixer();
211 | mixer.setOnAudioMixListener(new OnAudioMixListener() {
212 |
213 | boolean isFirst = false;
214 |
215 | public void onMixing(byte[] mixBytes) throws IOException {
216 | if(stopped){
217 | fosTempMixAudio.close();
218 | Message stopMsg = mEventHandler.obtainMessage(MSG_PLAY_STOP, mixFilePath);
219 | mEventHandler.sendMessage(stopMsg);
220 | throw new AudioMixException("stop play the mix audios.");
221 | }else{
222 | fosTempMixAudio.write(mixBytes);
223 | if(!isFirst){
224 | isFirst = true;
225 | mEventHandler.sendEmptyMessage(MSG_PLAY_START);
226 | waitForRecordStart();
227 | }
228 | audioTrack.write(mixBytes, 0, mixBytes.length);
229 | }
230 | }
231 |
232 | @Override
233 | public void onMixError(int errorCode) {
234 |
235 | if(fosTempMixAudio != null){
236 | try {
237 | fosTempMixAudio.close();
238 | } catch (IOException e) {
239 | e.printStackTrace();
240 | }
241 | }
242 | }
243 |
244 | public void onMixComplete() {
245 |
246 | if(fosTempMixAudio != null){
247 | try {
248 | fosTempMixAudio.close();
249 | } catch (IOException e) {
250 | e.printStackTrace();
251 | }
252 | }
253 |
254 | Message completeMsg = mEventHandler.obtainMessage(MSG_PLAY_COMPLETE, mixFilePath);
255 | mEventHandler.sendMessage(completeMsg);
256 | }
257 | });
258 | mixer.mixAudios(rawAudioFiles);
259 | } catch (FileNotFoundException e) {
260 | e.printStackTrace();
261 | }finally{
262 | audioTrack.stop();
263 | audioTrack.release();
264 | playing = false;
265 | synchronized (mLock) {
266 | mLock.notifyAll();
267 | }
268 | }
269 | }
270 | }
271 |
272 | public interface OnRawAudioPlayListener{
273 | void onPlayStart();
274 | void onPlayStop(String tempMixFile);
275 | void onPlayComplete(String tempMixFile);
276 | }
277 | }
278 |
--------------------------------------------------------------------------------
/src/com/musicplus/media/VideoMuxer.java:
--------------------------------------------------------------------------------
1 | package com.musicplus.media;
2 |
3 | import java.io.File;
4 | import java.io.FileInputStream;
5 | import java.io.IOException;
6 | import java.nio.ByteBuffer;
7 | import java.util.Arrays;
8 |
9 | import android.media.MediaCodec;
10 | import android.media.MediaCodec.BufferInfo;
11 | import android.media.MediaCodecInfo;
12 | import android.media.MediaExtractor;
13 | import android.media.MediaFormat;
14 | import android.media.MediaMuxer;
15 |
16 | import com.musicplus.app.MainApplication;
17 | import com.musicplus.utils.DLog;
18 |
19 | /**
20 | * 视频混合音频
21 | * @author Darcy
22 | */
23 | public abstract class VideoMuxer {
24 |
25 | private static final String TAG = "VideoMuxer";
26 |
27 | String mOutputVideo;
28 |
29 | private VideoMuxer(String outputVideo){
30 | this.mOutputVideo = outputVideo;
31 | }
32 |
33 | public final static VideoMuxer createVideoMuxer(String outputVideo){
34 | return new Mp4Muxer(outputVideo);
35 | }
36 |
37 | /**
38 | * mix raw audio into video
39 | * @param videoFile
40 | * @param rawAudioFile
41 | * @param includeAudioInVideo
42 | */
43 | public abstract void mixRawAudio(File videoFile,File rawAudioFile,boolean includeAudioInVideo);
44 |
45 | /**
46 | * use android sdk MediaMuxer
47 | * @author Darcy
48 | * @version API >= 18
49 | */
50 | private static class Mp4Muxer extends VideoMuxer{
51 |
52 | private final static String AUDIO_MIME = "audio/mp4a-latm";
53 | private final static long audioBytesPerSample = 44100*16/8;
54 |
55 | private long rawAudioSize;
56 |
57 | public Mp4Muxer(String outputVideo) {
58 | super(outputVideo);
59 | }
60 |
61 | @Override
62 | public void mixRawAudio(File videoFile, File rawAudioFile,boolean includeAudioInVideo) {
63 | final String videoFilePath = videoFile.getAbsolutePath();
64 |
65 | MediaMuxer videoMuxer = null;
66 | try {
67 |
68 | final String outputVideo = mOutputVideo;
69 | videoMuxer = new MediaMuxer(outputVideo,MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
70 |
71 | MediaFormat videoFormat = null;
72 | MediaExtractor videoExtractor = new MediaExtractor();
73 | videoExtractor.setDataSource(videoFilePath);
74 |
75 | for (int i = 0; i < videoExtractor.getTrackCount(); i++) {
76 | MediaFormat format = videoExtractor.getTrackFormat(i);
77 | String mime = format.getString(MediaFormat.KEY_MIME);
78 | if (mime.startsWith("video/")) {
79 | videoExtractor.selectTrack(i);
80 | videoFormat = format;
81 | break;
82 | }
83 | }
84 |
85 | int videoTrackIndex = videoMuxer.addTrack(videoFormat);
86 | int audioTrackIndex = 0;
87 |
88 | //extract and decode audio
89 | FileInputStream fisExtractAudio = null;
90 | if(includeAudioInVideo){
91 | AndroidAudioDecoder audioDecoder = new AndroidAudioDecoder(videoFilePath);
92 | String extractAudioFilePath = MainApplication.RECORD_AUDIO_PATH + "/" + System.currentTimeMillis();
93 | audioDecoder.decodeToFile(extractAudioFilePath);
94 |
95 | File extractAudioFile = new File(extractAudioFilePath);
96 | fisExtractAudio = new FileInputStream(extractAudioFile);
97 | }
98 | FileInputStream fisMixAudio = new FileInputStream(rawAudioFile);
99 |
100 | boolean readExtractAudioEOS = includeAudioInVideo ? false : true;
101 | boolean readMixAudioEOS = false;
102 | byte[] extractAudioBuffer = new byte[4096];
103 | byte[] mixAudioBuffer = new byte[4096];
104 | int extractAudioReadCount = 0;
105 | int mixAudioReadCount = 0;
106 |
107 | final MultiAudioMixer audioMixer = MultiAudioMixer.createAudioMixer();
108 | final byte[][] twoAudioBytes = new byte[2][];
109 |
110 | final MediaCodec audioEncoder = createACCAudioDecoder();
111 | audioEncoder.start();
112 |
113 | ByteBuffer[] audioInputBuffers = audioEncoder.getInputBuffers();
114 | ByteBuffer[] audioOutputBuffers = audioEncoder.getOutputBuffers();
115 | boolean sawInputEOS = false;
116 | boolean sawOutputEOS = false;
117 | long audioTimeUs = 0 ;
118 | BufferInfo outBufferInfo = new BufferInfo();
119 |
120 | int inputBufIndex, outputBufIndex;
121 | while(!sawOutputEOS){
122 | if (!sawInputEOS) {
123 | inputBufIndex = audioEncoder.dequeueInputBuffer(10000);
124 | if (inputBufIndex >= 0) {
125 | ByteBuffer inputBuffer = audioInputBuffers[inputBufIndex];
126 | inputBuffer.clear();
127 |
128 | int bufferSize = inputBuffer.remaining();
129 | if(bufferSize != extractAudioBuffer.length){
130 | extractAudioBuffer = new byte[bufferSize];
131 | mixAudioBuffer = new byte[bufferSize];
132 | }
133 |
134 | if(!readExtractAudioEOS){
135 | extractAudioReadCount = fisExtractAudio.read(extractAudioBuffer);
136 | if(extractAudioReadCount == -1){
137 | readExtractAudioEOS = true;
138 | }
139 | }
140 |
141 | if(!readMixAudioEOS){
142 | mixAudioReadCount = fisMixAudio.read(mixAudioBuffer);
143 | if(mixAudioReadCount == -1){
144 | readMixAudioEOS = true;
145 | }
146 | }
147 |
148 | if(readExtractAudioEOS && readMixAudioEOS){
149 | audioEncoder.queueInputBuffer(inputBufIndex,0 , 0 , 0 ,MediaCodec.BUFFER_FLAG_END_OF_STREAM);
150 | sawInputEOS = true;
151 | }else{
152 |
153 | byte[] mixAudioBytes;
154 | if(!readExtractAudioEOS && !readMixAudioEOS){
155 | if(extractAudioReadCount == mixAudioReadCount){
156 | twoAudioBytes[0] = extractAudioBuffer;
157 | twoAudioBytes[1] = mixAudioBuffer;
158 | }else if(extractAudioReadCount > mixAudioReadCount){
159 | twoAudioBytes[0] = extractAudioBuffer;
160 | Arrays.fill(mixAudioBuffer, mixAudioReadCount -1, bufferSize, (byte)0);
161 | }else{
162 | Arrays.fill(extractAudioBuffer, extractAudioReadCount -1, bufferSize, (byte)0);
163 | }
164 | mixAudioBytes = audioMixer.mixRawAudioBytes(twoAudioBytes);
165 | if(mixAudioBytes == null){
166 | DLog.e(TAG, "mix audio : null");
167 | }
168 | inputBuffer.put(mixAudioBytes);
169 | rawAudioSize += mixAudioBytes.length;
170 | audioEncoder.queueInputBuffer(inputBufIndex, 0, mixAudioBytes.length, audioTimeUs, 0);
171 | }else if(!readExtractAudioEOS && readMixAudioEOS){
172 | inputBuffer.put(extractAudioBuffer, 0, extractAudioReadCount);
173 | rawAudioSize += extractAudioReadCount;
174 | audioEncoder.queueInputBuffer(inputBufIndex, 0, extractAudioReadCount, audioTimeUs, 0);
175 | }else{
176 | inputBuffer.put(mixAudioBuffer, 0, mixAudioReadCount);
177 | rawAudioSize += mixAudioReadCount;
178 | audioEncoder.queueInputBuffer(inputBufIndex, 0, mixAudioReadCount, audioTimeUs, 0);
179 | }
180 |
181 | audioTimeUs = (long) (1000000 * (rawAudioSize / 2.0) / audioBytesPerSample);
182 | }
183 | }
184 | }
185 |
186 | outputBufIndex = audioEncoder.dequeueOutputBuffer(outBufferInfo, 10000);
187 | if(outputBufIndex >= 0){
188 |
189 | // Simply ignore codec config buffers.
190 | if ((outBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG)!= 0) {
191 | DLog.i(TAG, "audio encoder: codec config buffer");
192 | audioEncoder.releaseOutputBuffer(outputBufIndex, false);
193 | continue;
194 | }
195 |
196 | if(outBufferInfo.size != 0){
197 | ByteBuffer outBuffer = audioOutputBuffers[outputBufIndex];
198 | outBuffer.position(outBufferInfo.offset);
199 | outBuffer.limit(outBufferInfo.offset + outBufferInfo.size);
200 | DLog.i(TAG, String.format(" writing audio sample : size=%s , presentationTimeUs=%s", outBufferInfo.size, outBufferInfo.presentationTimeUs));
201 | if(lastAudioPresentationTimeUs < outBufferInfo.presentationTimeUs){
202 | videoMuxer.writeSampleData(audioTrackIndex, outBuffer, outBufferInfo);
203 | lastAudioPresentationTimeUs = outBufferInfo.presentationTimeUs;
204 | }else{
205 | DLog.e(TAG, "error sample! its presentationTimeUs should not lower than before.");
206 | }
207 | }
208 |
209 | audioEncoder.releaseOutputBuffer(outputBufIndex, false);
210 |
211 | if ((outBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
212 | sawOutputEOS = true;
213 | }
214 | }else if (outputBufIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
215 | audioOutputBuffers = audioEncoder.getOutputBuffers();
216 | } else if (outputBufIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
217 | MediaFormat audioFormat = audioEncoder.getOutputFormat();
218 | audioTrackIndex = videoMuxer.addTrack(audioFormat);
219 | videoMuxer.start(); //start muxer
220 | }
221 | }
222 |
223 | if(fisExtractAudio != null){
224 | fisExtractAudio.close();
225 | }
226 |
227 | fisMixAudio.close();
228 | audioEncoder.stop();
229 | audioEncoder.release();
230 |
231 | //mix video
232 | boolean videoMuxDone = false;
233 | // 压缩帧大小 < 原始图片大小
234 | int videoWidth = videoFormat.getInteger(MediaFormat.KEY_WIDTH);
235 | int videoHeight = videoFormat.getInteger(MediaFormat.KEY_HEIGHT);
236 | ByteBuffer videoSampleBuffer = ByteBuffer.allocateDirect(videoWidth * videoHeight);
237 | BufferInfo videoBufferInfo = new BufferInfo();
238 | int sampleSize;
239 | while (!videoMuxDone) {
240 | videoSampleBuffer.clear();
241 | sampleSize = videoExtractor.readSampleData(videoSampleBuffer, 0);
242 | if (sampleSize < 0) {
243 | videoMuxDone = true;
244 | } else {
245 | videoBufferInfo.presentationTimeUs = videoExtractor.getSampleTime();
246 | videoBufferInfo.flags = videoExtractor.getSampleFlags();
247 | videoBufferInfo.size = sampleSize;
248 | videoSampleBuffer.limit(sampleSize);
249 | videoMuxer.writeSampleData(videoTrackIndex, videoSampleBuffer,videoBufferInfo);
250 | videoExtractor.advance();
251 | }
252 | }
253 |
254 | videoExtractor.release();
255 | } catch (IOException e) {
256 | e.printStackTrace();
257 | }finally{
258 | if(videoMuxer != null){
259 | videoMuxer.stop();
260 | videoMuxer.release();
261 | DLog.i(TAG, "video mix complete.");
262 | }
263 | }
264 | }
265 |
266 | private MediaCodec createACCAudioDecoder() throws IOException {
267 | MediaCodec codec = MediaCodec.createEncoderByType(AUDIO_MIME);
268 | MediaFormat format = new MediaFormat();
269 | format.setString(MediaFormat.KEY_MIME, AUDIO_MIME);
270 | format.setInteger(MediaFormat.KEY_BIT_RATE, 128000);
271 | format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 2);
272 | format.setInteger(MediaFormat.KEY_SAMPLE_RATE, 44100);
273 | format.setInteger(MediaFormat.KEY_AAC_PROFILE,
274 | MediaCodecInfo.CodecProfileLevel.AACObjectLC);
275 | codec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
276 | return codec;
277 | }
278 |
279 | private long lastAudioPresentationTimeUs = -1;
280 | }
281 | }
282 |
--------------------------------------------------------------------------------
/src/com/musicplus/media/VideoRecorder.java:
--------------------------------------------------------------------------------
1 | package com.musicplus.media;
2 |
3 | import java.io.IOException;
4 | import java.util.List;
5 | import java.util.concurrent.BrokenBarrierException;
6 | import java.util.concurrent.CyclicBarrier;
7 |
8 | import android.app.Activity;
9 | import android.hardware.Camera;
10 | import android.media.MediaRecorder;
11 | import android.os.AsyncTask;
12 | import android.view.Surface;
13 | import android.view.SurfaceView;
14 |
15 | /**
16 | * video recoder
17 | *
18 | * @author Darcy
19 | */
20 | public class VideoRecorder {
21 |
22 | private Activity mActivity;
23 | private MediaRecorder mMediaRecorder;
24 | private SurfaceView mPreview;
25 | private int mVidoeWidth,mVideoHeight;
26 | private Camera mCamera;
27 | private String mOutputFile;
28 |
29 | private OnVideoRecordListener mListener;
30 | private volatile boolean isRecording = false;
31 | private boolean isValidCamera;
32 |
33 | private CyclicBarrier mRecordBarrier; //同步背景音乐
34 |
35 | public VideoRecorder(Activity activity , SurfaceView preview, String outputFile, CyclicBarrier recordBarrier) {
36 | this.mActivity = activity;
37 | this.mPreview = preview;
38 | this.mOutputFile = outputFile;
39 | this.mRecordBarrier = recordBarrier;
40 | }
41 |
42 | public void setOnVideoRecordListener(OnVideoRecordListener l){
43 | this.mListener = l;
44 | }
45 |
46 | public void startPreview(){
47 | try {
48 | prepareCamera();
49 | mCamera.startPreview();
50 | isValidCamera = true;
51 | } catch (IOException e) {
52 | e.printStackTrace();
53 | isValidCamera = false;
54 | }
55 | }
56 |
57 | public void startRecord() {
58 | if(isValidCamera && !isRecording){
59 | isRecording = true;
60 | new MediaRecordTask().execute();
61 | }
62 | }
63 |
64 | class MediaRecordTask extends AsyncTask {
65 |
66 | @Override
67 | protected Boolean doInBackground(Void... voids) {
68 | if (prepareVideoRecorder()) {
69 | if(mRecordBarrier != null){
70 | try {
71 | mRecordBarrier.await();
72 | mMediaRecorder.start();
73 | return true;
74 | } catch (InterruptedException e) {
75 | e.printStackTrace();
76 | } catch (BrokenBarrierException e) {
77 | e.printStackTrace();
78 | }
79 | return false;
80 | }else{
81 | mMediaRecorder.start();
82 | return true;
83 | }
84 | } else {
85 | isRecording = false;
86 | release();
87 | return false;
88 | }
89 | }
90 |
91 | @Override
92 | protected void onPostExecute(Boolean result) {
93 | if(result){
94 | if(mListener!= null)
95 | mListener.onStarted();
96 | }
97 | }
98 | }
99 |
100 | private void prepareCamera() throws IOException{
101 | mCamera = Camera.open();
102 | setCameraDisplayOrientation(mActivity, Camera.CameraInfo.CAMERA_FACING_BACK, mCamera);
103 |
104 | int previewWidth = mPreview.getWidth();
105 | int previewHeight = mPreview.getHeight();
106 | Camera.Parameters parameters = mCamera.getParameters();
107 | List supportedPreviewSizes = parameters.getSupportedPreviewSizes();
108 | parameters.setPreviewSize(640,480);
109 | parameters.setRotation(90);
110 | mCamera.setParameters(parameters);
111 | mVidoeWidth = 640;
112 | mVideoHeight = 480;
113 |
114 | mCamera.setPreviewDisplay(mPreview.getHolder());
115 | }
116 |
117 | private void setCameraDisplayOrientation(Activity activity,
118 | int cameraId, android.hardware.Camera camera) {
119 | android.hardware.Camera.CameraInfo info = new android.hardware.Camera.CameraInfo();
120 | android.hardware.Camera.getCameraInfo(cameraId, info);
121 | int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();
122 | int degrees = 0;
123 | switch (rotation) {
124 | case Surface.ROTATION_0: degrees = 0; break;
125 | case Surface.ROTATION_90: degrees = 90; break;
126 | case Surface.ROTATION_180: degrees = 180; break;
127 | case Surface.ROTATION_270: degrees = 270; break;
128 | }
129 |
130 | int result;
131 | if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
132 | result = (info.orientation + degrees) % 360;
133 | result = (360 - result) % 360; // compensate the mirror
134 | } else { // back-facing
135 | result = (info.orientation - degrees + 360) % 360;
136 | }
137 | camera.setDisplayOrientation(result);
138 | }
139 |
140 | private boolean prepareVideoRecorder() {
141 |
142 | mMediaRecorder = new MediaRecorder();
143 |
144 | mCamera.unlock();
145 | mMediaRecorder.setCamera(mCamera);
146 | mMediaRecorder.setOrientationHint(90);
147 |
148 | mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
149 | mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
150 | mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
151 |
152 | mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
153 | mMediaRecorder.setAudioChannels(MediaConstants.AUDIO_CHANNEL);
154 | mMediaRecorder.setAudioSamplingRate(MediaConstants.AUDIO_SAMPLE_RATE);
155 |
156 | mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
157 | mMediaRecorder.setVideoFrameRate(MediaConstants.VIDEO_FRAME_RATE);
158 | mMediaRecorder.setVideoSize(mVidoeWidth, mVideoHeight);
159 | mMediaRecorder.setVideoEncodingBitRate(8000000);
160 |
161 | mMediaRecorder.setOutputFile(mOutputFile);
162 |
163 | try {
164 | mMediaRecorder.prepare();
165 | } catch (IllegalStateException e) {
166 | release();
167 | return false;
168 | } catch (IOException e) {
169 | release();
170 | return false;
171 | }
172 | return true;
173 | }
174 |
175 | public void release() {
176 | if (mMediaRecorder != null) {
177 | mMediaRecorder.reset();
178 | mMediaRecorder.release();
179 | mMediaRecorder = null;
180 | }
181 |
182 | if (mCamera != null) {
183 | mCamera.stopPreview();
184 | mCamera.release();
185 | mCamera = null;
186 | }
187 |
188 | isRecording = false;
189 | }
190 |
191 | public interface OnVideoRecordListener{
192 | void onStarted();
193 | void onError(int errorCode);
194 | }
195 | }
196 |
--------------------------------------------------------------------------------
/src/com/musicplus/utils/DLog.java:
--------------------------------------------------------------------------------
1 | package com.musicplus.utils;
2 |
3 | import android.util.Log;
4 |
5 | public class DLog {
6 |
7 | private static boolean debug = true;
8 |
9 | private static final String TAG = "com.darcymusic";
10 |
11 | public static void i(String tag, String msg){
12 | if(debug)
13 | Log.i(tag, msg);
14 | }
15 |
16 | public static void i(String msg){
17 | if(debug)
18 | Log.i(TAG, msg);
19 | }
20 |
21 | public static void w(String tag, String msg){
22 | if(debug)
23 | Log.w(tag, msg);
24 | }
25 |
26 | public static void w(String msg){
27 | if(debug)
28 | Log.w(TAG, msg);
29 | }
30 |
31 | public static void e(String tag, String msg){
32 | if(debug)
33 | Log.e(tag, msg);
34 | }
35 |
36 | public static void e(String msg){
37 | if(debug)
38 | Log.e(TAG, msg);
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/com/musicplus/utils/MD5Util.java:
--------------------------------------------------------------------------------
1 |
2 | package com.musicplus.utils;
3 |
4 | import java.security.MessageDigest;
5 |
6 | /*************************************************
7 | * md5 类实现了RSA Data Security, Inc.在提交给IETF 的RFC1321中的MD5 message-digest 算法。
8 | *************************************************/
9 |
10 | public class MD5Util {
11 |
12 | public static String getMD5Str(String src) {
13 | try {
14 | MessageDigest messageDigest = MessageDigest.getInstance("MD5");
15 | messageDigest.update(src.getBytes());
16 | return toHexString(messageDigest.digest());
17 | } catch (Exception e) {
18 | }
19 | return null;
20 | }
21 |
22 | private static String toHexString(byte[] b) {
23 | StringBuilder sb = new StringBuilder(b.length * 2);
24 | for (int i = 0; i < b.length; i++) {
25 | sb.append(Digit[(b[i] & 0xf0) >>> 4]);
26 | sb.append(Digit[b[i] & 0x0f]);
27 | }
28 | return sb.toString();
29 | }
30 |
31 | private static char[] Digit = {
32 | '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
33 | };
34 |
35 | }
36 |
--------------------------------------------------------------------------------