赞
踩
Note: 自定义UI文件名和视图id需要和library中提供的保持一致
项目中通过ConcatenatingMediaSource + ProgressiveMediaSource播放playlist,媒体源是服务端的mp3格式的音频文件,暂时只研究了这一部分的音频播放逻辑。
根据项目中的实际应用大致画了一下流程图如下:
采样流程:ProgressiveMediaPeriod在startLoading方法中会通过Loader对象在后台线程执行内部类ExtractingLoadable.load方法开始加载media data。 然后交由extractor.read()处理,最终由trackOutput(SampleQueue)真正开始从ExtractorInput中采样。采样的media data会保存到SampleDataQueue中,SampleDataQueue相当于是一个容器。
播放流程:入口在ExoPlayerImplInternal的doSomeWork()方法, 通过Renderer(MediaCodecAudioRenderer)对象render方法渲染音频。关键的就是drainOutputBuffer()方法和feedInputBuffer()方法。drainOutputBuffer取出buffer数据通过processOutputBuffer()写到AudioTrack。 feedInputBuffer则会读取采样流程中SampleDataQueue中的数据保存buffer中。doSomeWork方法会定期执行,从而实现音频播放
还是将采样和播放流程分开,代码只截取了关键部分,重要的地方自己加了注释
采样代码入口ProgressiveMediaPeriod.startLoading()
:
private void startLoading() {
// ExtractingLoadable是ProgressiveMediaPeriod的内部类
ExtractingLoadable loadable =
new ExtractingLoadable(
uri, dataSource, extractorHolder, /* extractorOutput= */ this, loadCondition);
...
// 将loadable交由loader对象的后台线程执行
long elapsedRealtimeMs = loader.startLoading(loadable, this, loadErrorHandlingPolicy.getMinimumLoadableRetryCount(dataType));
...
}
接下来就会在后台线程执行ExtractingLoadable
任务
// 这里是load方法开始加载数据 public void load() throws IOException, InterruptedException { int result = Extractor.RESULT_CONTINUE; ExtractorInput input = null; // 封装ExtractorInput,持有了一个dataSource(底层是DefaultHttpDataSource)对象,真正从网络上获取数据由DefaultHttpDataSource实现 DataSource extractorDataSource = dataSource; input = new DefaultExtractorInput(extractorDataSource, position, length); // 选择具体的Extractor并初始化,项目中对应的就是Mp3Extractor Extractor extractor = extractorHolder.selectExtractor(input, extractorOutput, uri); while (result == Extractor.RESULT_CONTINUE && !loadCanceled) { loadCondition.block(); // extractor开始读取input数据 result = extractor.read(input, positionHolder); } ... } // 选择一个具体的Extractor public Extractor selectExtractor(ExtractorInput input, ExtractorOutput output, Uri uri) throws IOException, InterruptedException { ... for (Extractor extractor : extractors) { if (extractor.sniff(input)) { this.extractor = extractor; break; } } // 初始化extractor,这里的output就是ExtractingLoadable引用的外层的ProgressiveMediaPeriod对象, // extractor会通过output得到一个trackOutput也就是SampleQueue对象,所以extractor和ProgressiveMediaPeriod都持有同一个SampleQueue对象 extractor.init(output); return extractor; }
Mp3Extractor.readSample()
开始采样数据:
private int readSample(ExtractorInput extractorInput) throws IOException, InterruptedException {
...
// extractorInput最后会交由trackOutput真正开始采样
int bytesAppended = trackOutput.sampleData(extractorInput, sampleBytesRemaining, true);
...
}
这里的trackOutput
也就是SampleQueue
对象, 它在Mp3Extractor.init()
方法中初始化也就是上面selectExtractor
方法中被调用的:
@Override
public void init(ExtractorOutput output) {
extractorOutput = output;
// extractorOutput就是ProgressiveMediaPeriod
trackOutput = extractorOutput.track(0, C.TRACK_TYPE_AUDIO);
extractorOutput.endTracks();
}
后面就会由SampleQueue读取extractorInput数据, SampleQueue也只是一个外层的wrapper容器,真正保存数据的是SampleDataQueue,下面是SampleDataQueue sampleData代码:
public int sampleData(ExtractorInput input, int length, boolean allowEndOfInput) throws IOException, InterruptedException {
length = preAppend(length);
// 读取sample media data
int bytesAppended =
input.read(
writeAllocationNode.allocation.data,
writeAllocationNode.translateOffset(totalBytesWritten),
length);
...
postAppend(bytesAppended);
return bytesAppended;
}
这里是真正采样media data的地方。input是在ExtractingLoadable.load中创建的DefaultExtractorInput对象,它持有dataSource的引用。我们是需要网络上的数据,所以最终的dataSource是由DefaultHttpDataSourceFactory创建的DefaultHttpDataSource对象。通过input读取数据的时候实际就是由DefaultHttpDataSource建立网络链接读取网络数据,这一部分就不贴代码了。
接下来是播放流程的代码,入口是ExoPlayerImplInternal
的doSomeWork
方法,这个方法会周期性地执行,应该算是最重要的一个方法。方法内调用renderer.render开始渲染media data. Renderer是一个接口,我们这里需要的是它的实现类MediaCodecAudioRenderer
。MediaCodecAudioRenderer没有重写render方法,下面是父类MediaCodecRenderer
render方法的代码:
@Override
public void render(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException {
...
if (codec != null) {
// 将buffer数据写到AudioTrack
while (drainOutputBuffer(positionUs, elapsedRealtimeUs)) {}
// 从SampleQueue中读取sample data到buffer
while (feedInputBuffer() && shouldContinueFeeding(drainStartTimeMs)) {}
TraceUtil.endSection();
}
...
}
drainOutputBuffer
方法内会通过processOutputBuffer
方法将buffer数据传递给子类(这里就是MediaCodecAudioRenderer
)处理,然后会通过DefaultAudioSink
handle,最终会将数据写到AudioTrack中去,音频由AudioTrack播放,代码如下:
// 这里是MediaCodecRenderer方法 private boolean drainOutputBuffer(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException {\ ... // 调用抽象方法processOutputBuffer,由子类处理输出 processedOutputBuffer = processOutputBuffer( positionUs, elapsedRealtimeUs, codec, outputBuffer, outputIndex, outputBufferInfo.flags, outputBufferInfo.presentationTimeUs, isDecodeOnlyOutputBuffer, isLastOutputBuffer, outputFormat); ... } // 这里是子类MediaCodecAudioRenderer方法,参数太多没贴 protected boolean processOutputBuffer(....) throws ExoPlaybackException { ... // buffer交由audioSink(DefaultAudioSink)处理, audioSink内部会将buffer的media data写到AudioTrack if (audioSink.handleBuffer(buffer, bufferPresentationTimeUs)) { codec.releaseOutputBuffer(bufferIndex, false); decoderCounters.renderedOutputBufferCount++; return true; } ... }
接下来是feedInputBuffer
部分的代码,它从采样流程中用来保存sample data的SampleQueue中读数据到自己的buffer中。下面是相关部分代码:
// 这里是MediaCodecRenderer方法
private boolean feedInputBuffer() throws ExoPlaybackException {
// 这里调用父类BaseRenderer方法读数据到buffer
result = readSource(formatHolder, buffer, false);
}
// 这里是BaseRenderer方法
protected final int readSource(
FormatHolder formatHolder, DecoderInputBuffer buffer, boolean formatRequired) {
// 通过stream对象读取sample data到buffer
int result = stream.readData(formatHolder, buffer, formatRequired);
...
}
这里的stream是
SampleStream
接口,实际是ProgressiveMediaPeriod内部类SampleStreamImpl
对象。它是在enable renderer的时候传入的MediaPeriodHolder
持有的SampleStream
对象, 而MediaPeriodHolder.sampleStreams保存的是由ProgressiveMediaPeriod创建的内部类SampleStreamImpl对象。 所以通过enable renderer就将SampleStreamImpl对象传给了renderer,renderer就可以通过stream来读sample data, 具体代码就不贴了,感兴趣的可以从ExoPlayerImplInternal.enableRenderers()方法开始看。
接着上面BaseRenderer.readSource流程,他会走到SampleStreamImpl(ProgressiveMediaPeriod内部类) readData中:
// 这是SampleStreamImpl方法
public int readData(FormatHolder formatHolder, DecoderInputBuffer buffer, boolean formatRequired) {
// 只有一行代码, 调用外层类readData
return ProgressiveMediaPeriod.this.readData(track, formatHolder, buffer, formatRequired);
}
// 这是ProgressiveMediaPeriod方法
int readData(int sampleQueueIndex, FormatHolder formatHolder,
DecoderInputBuffer buffer, boolean formatRequired) {
...
// 这里就是从SampleQueue中读sample data
int result = sampleQueues[sampleQueueIndex].read(formatHolder, buffer, formatRequired, loadingFinished, lastSeekPositionUs);
...
}
总体的流程就是这些了。 其他不同格式的数据稍微看了几眼,流程基本上大同小异。
总结:音频播放主要是有采样和播放两条线。 有一个common的容器SampleQueue, 采样过程从data source读取sample data保存到SampleQueue,播放过程则是renderer通过SampleStream从SampleQueue中读数据到buffer, 然后将数据给具体的media api消费,比如这里的音频播放则是由AudioTrack消费buffer数据。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。