最新版本Exoplayer(MediaX)实现K歌原伴唱包括单音轨和双音轨
在做K歌类项目中,原伴唱切换是必不可少的基础功能,一般一首完整的歌曲要包含MV视频+原唱音频+伴唱音频+歌词。
而原伴唱音频有两种形式:
1.双音轨:音轨一是原唱,音轨二是伴唱
2.单音轨:左声道是原唱,右声道是伴唱
至于哪个音轨或声道是原唱还是伴唱由制作歌曲的时候觉得,不同的文件可能是反的,所以一般的做法是人工对歌曲列表做标识,标明该歌曲哪一个音轨或者声道是原唱,在切换原伴唱时先获取该标识就可以知道该使用哪个音轨或者声道了。
使用Exoplayer实现原伴唱切换功能,对于双音轨歌曲很简单,有现成的接口调用进行音轨切换就可以了,对于单音轨歌曲要切换左右声道Exoplayer并没有提供现成的接口,网上也有一些方法实现,但基本上都用不了了,要么是接口被舍弃了,要么就是达不到想要的效果。
这里提供一种切实可行的方式实现单音轨音频实现切换左右声道。
因为Exoplayer支持FFmpeg扩展进行音频软解码,那么就可以在FFmpeg进行音频解码的时候对元数据进行处理来实现:仅播放左声道、仅播放右声道、播放立体声、交换左右声道,当然就有了原伴唱效果。
在上一篇文章中详细介绍了Exoplayer如何扩展FFmpeg实现音频软解码最新版本Exoplayer扩展FFmpeg音频软解码保姆级教程
那么可以在此基础上进行修改,加上单音轨原伴唱功能,上一篇文件最终我们是得到了extension-ffmpeg-release.aar来工AndroId调用,那么久还是需要修改Exoplayer中的decoder_ffpeg模块源码。先来说大致流程:
1.修改ffmpeg_jni.cc,decodePacket()函数中增加单音轨文件声道处理逻辑,并增加setChannelMode()开放给Android调用
2.修改FfmpegLibrary.java,新增native方法setChannelMode()对应JNI函数,供Exoplayer播放时调用
3.重新编译Exoplayer中的decoder_ffpeg模块,得到修改后的extension-ffmpeg-release.aar
3.Android集成aar,在播放器(一般对Exoplayer进行封装,实现自己的播放逻辑,比如我封装了ExoPlayerImpl.java)中实现切换原伴唱逻辑
这里贴出ExoPlayerImpl.java切换原伴唱部分源码供参考:
@Override
public void playVoice(int selectedTrackIndex,int singleMode) {
/** singlemode 得到歌曲的原伴唱信息(单音轨---1:右声道原唱;2:左声道原唱 多音轨---1:第一音轨原唱;2:第二音轨原唱)
* selectedTrackIndex 设置原唱或者伴唱: 0:yuan chang 1:ban chang 2:STEREO 3: L/R exchange
*/
Log.i(TAG, "=selectedTrackIndex=" + selectedTrackIndex +"=singleMode="+singleMode+ "=audioList size=" + audioList.size());
//切换原伴唱mode: 0-原唱,1-伴唱
int rendererIndex = 1; // 音频渲染器的索引通常是 1
// int selectedTrackIndex = 1; // 需要切换到的音轨索引
// 获取音轨列表
MappingTrackSelector.MappedTrackInfo currentMappedTrackInfo = trackSelector.getCurrentMappedTrackInfo();
TrackGroupArray trackGroups = currentMappedTrackInfo.getTrackGroups(rendererIndex);
if (audioList.size() > 1) { //多音轨切换
// 用户选择第 index 个音轨
TrackGroup selectedGroup = trackGroups.get(selectedTrackIndex);
// 需要切换到的音轨索引
TrackSelectionOverride override = new TrackSelectionOverride(selectedGroup, 0);
// 应用新音轨
TrackSelectionParameters params = mExoPlayer.getTrackSelectionParameters()
.buildUpon()
.setOverrideForType(override)
.build();
mExoPlayer.setTrackSelectionParameters(params);
} else {//单音轨,切换左右声道
// mExoPlayer.getAudioComponent().setVolume(selectedTrackIndex == 1 ? 0.3f : 1f);
/* enum ChannelMode {
CHANNEL_MODE_NORMAL, // 正常播放 0
CHANNEL_MODE_LEFT_ONLY, // 仅左声道 1
CHANNEL_MODE_RIGHT_ONLY,// 仅右声道 2
CHANNEL_MODE_SWAP // 交换左右声道 3
};*/
switch (selectedTrackIndex){
case 0: //原唱
ffmpegAudioRenderer.setChannelMode(singleMode==1?2:1);
break;
case 1: //伴唱
ffmpegAudioRenderer.setChannelMode(singleMode==1?1:2);
break;
case 2: //立体声
ffmpegAudioRenderer.setChannelMode(0);
break;
case 3: //交换左右声道
ffmpegAudioRenderer.setChannelMode(3);
break;
}
}
}
public class PluginRenderFactory extends DefaultRenderersFactory {
/**
* @param context A {@link Context}.
*/
public PluginRenderFactory(Context context) {
super(context);
}
@Override
protected void buildAudioRenderers(Context context, int extensionRendererMode, MediaCodecSelector mediaCodecSelector, boolean enableDecoderFallback, AudioSink audioSink, Handler eventHandler, AudioRendererEventListener eventListener, ArrayList<Renderer> out) {
ffmpegAudioRenderer = new FfmpegAudioRenderer(eventHandler, eventListener, audioSink);
out.add(ffmpegAudioRenderer);
super.buildAudioRenderers(context, extensionRendererMode, mediaCodecSelector, enableDecoderFallback, audioSink, eventHandler, eventListener, out);
}
}
// 初始化 ExoPlayer
trackSelector = new DefaultTrackSelector(context);
DefaultRenderersFactory renderersFactory = new PluginRenderFactory(context)
.setExtensionRendererMode(DefaultRenderersFactory.EXTENSION_RENDERER_MODE_ON);
mExoPlayer = new ExoPlayer.Builder(context, renderersFactory)
.setTrackSelector(trackSelector)
.build();
编译好的aar或源码点击这里