关联博客
ExoPlayer播放器剖析(一)进入ExoPlayer的世界
ExoPlayer播放器剖析(二)编写exoplayer的demo
ExoPlayer播放器剖析(三)流程分析—从build到prepare看ExoPlayer的创建流程
ExoPlayer播放器剖析(四)从renderer.render函数分析至MediaCodec
ExoPlayer播放器剖析(五)ExoPlayer对AudioTrack的操作
ExoPlayer播放器剖析(六)ExoPlayer同步机制分析
ExoPlayer播放器剖析(七)ExoPlayer对音频时间戳的处理
ExoPlayer播放器扩展(一)DASH流与HLS流简介
一、引言:
上一篇博客中,分析了doSomeWork中的renderer.render接口和startRenderers方法,其中,在介绍后者的时候有提过对音频的操作实际上调用了AudioTrack的play()方法进行播放,这篇博客着重介绍下ExoPlayer如何创建AudioTrack并往audiotrack中写数据的。
二、drainOutputBuffer函数分析:
对audiotrack的操作是在renderer.render中完成的,回到render方法, 我们曾贴出过下面的代码片段:
@Override
public void render(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException {
/* 是否处理EOS */
if (pendingOutputEndOfStream) {
pendingOutputEndOfStream = false;
processEndOfStream();
}
...
// We have a format.
/* 配置codec */
maybeInitCodecOrBypass();
...
TraceUtil.beginSection("drainAndFeed");
/* 消耗解码后的数据 */
while (drainOutputBuffer(positionUs, elapsedRealtimeUs)
&& shouldContinueRendering(renderStartTimeMs)) {
}
/* 填充源数据 */
while (feedInputBuffer() && shouldContinueRendering(renderStartTimeMs)) {
}
TraceUtil.endSection();
...
}
接下来,看下drainOutputBuffer函数:
private boolean drainOutputBuffer(long positionUs, long elapsedRealtimeUs)
throws ExoPlaybackException {
/* 判断是否有可用的输出buffer */
if (!hasOutputBuffer()) {
int outputIndex;
if (codecNeedsEosOutputExceptionWorkaround && codecReceivedEos) {
try {
outputIndex = codecAdapter.dequeueOutputBufferIndex(outputBufferInfo);
} catch (IllegalStateException e) {
processEndOfStream();
if (outputStreamEnded) {
// Release the codec, as it's in an error state.
releaseCodec();
}
return false;
}
} else {
/* 从decoder出列一个可用的buffer index */
outputIndex = codecAdapter.dequeueOutputBufferIndex(outputBufferInfo);
}
if (outputIndex < 0) {
if (outputIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED /* (-2) */) {
processOutputMediaFormatChanged();
return true;
} else if (outputIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED /* (-3) */) {
processOutputBuffersChanged();
return true;
}
/* MediaCodec.INFO_TRY_AGAIN_LATER (-1) or unknown negative return value */
if (codecNeedsEosPropagation
&& (inputStreamEnded || codecDrainState == DRAIN_STATE_WAIT_END_OF_STREAM)) {
processEndOfStream();
}
return false;
}
// We've dequeued a buffer.
if (shouldSkipAdaptationWorkaroundOutputBuffer) {
shouldSkipAdaptationWorkaroundOutputBuffer = false;
codec.releaseOutputBuffer(outputIndex, false);
return true;
} else if (outputBufferInfo.size == 0
&& (outputBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
// The dequeued buffer indicates the end of the stream. Process it immediately.
processEndOfStream();
return false;
}
/* 根据index找到对应的输出buffer */
this.outputIndex = outputIndex;
outputBuffer = getOutputBuffer(outputIndex);
// The dequeued buffer is a media buffer. Do some initial setup.
// It will be processed by calling processOutputBuffer (possibly multiple times).
if (outputBuffer != null) {
outputBuffer.position(outputBufferInfo.offset);
outputBuffer.limit(outputBufferInfo.offset + outputBufferInfo.size);
}
isDecodeOnlyOutputBuffer = isDecodeOnlyBuffer(outputBufferInfo.presentationTimeUs);
isLastOutputBuffer =
lastBufferInStreamPresentationTimeUs == outputBufferInfo.presentationTimeUs;
updateOutputFormatForTime(outputBufferInfo.presentationTimeUs);
}
boolean processedOutputBuffer;
if (codecNeedsEosOutputExceptionWorkaround && codecReceivedEos) {
try {
processedOutputBuffer =
processOutputBuffer(
positionUs,
elapsedRealtimeUs,
codec,
outputBuffer,
outputIndex,
outputBufferInfo.flags,
/* sampleCount= */ 1,
outputBufferInfo.presentationTimeUs,
isDecodeOnlyOutputBuffer,
isLastOutputBuffer,
outputFormat);
} catch (IllegalStateException e) {
processEndOfStream();
if (outputStreamEnded) {
// Release the codec, as it's in an error state.
releaseCodec();
}
return false;
}
} else {
/* 处理输出buffer */
processedOutputBuffer =
processOutputBuffer(
positionUs,
elapsedRealtimeUs,
codec,
outputBuffer,
outputIndex,
outputBufferInfo.flags,
/* sampleCount= */ 1,
outputBufferInfo.presentationTimeUs,
isDecodeOnlyOutputBuffer,
isLastOutputBuffer,
outputFormat);
}
/* 处理输出buffer结果为true,表示该outputbuffer已经被消耗完,若为false,表示该输出buffer还有残留数据 */
if (processedOutputBuffer) {
onProcessedOutputBuffer(outputBufferInfo.presentationTimeUs);
boolean isEndOfStream = (outputBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0;
/* 将outputBuffer置为空,且buffer的index置为-1,以便下次进入该函数去获取新的输出buffer */
resetOutputBuffer();
/* 判断是否需要处理EOS */
if (!isEndOfStream) {
return true;
}
processEndOfStream();
}
return false;
}
整个函数看起来很长,实际上做的主要事情就两件,先确认是否有可用的输出buffer,如果没有,则通过调用MediaCodec的api拿到可用buffer的index,然后通过index找到对应的buffer,在拿到可用的buffer之后,调用processOutputBuffer去处理输出buffer。先看下确定是否有可用buffer的判断条件:
private boolean hasOutputBuffer() {
return outputIndex >= 0;
}
因为outputIndex是通过调用MediaCodec.dequeueOutputBufferIndex()接口获得的,如果该值大于等于0,表明现在decoder里面已经解码出来了待播放的数据。接下来就会调用processOutputBuffer函数进行输出buffer的处理了,先说下对于处理结果的分析(重要),exoplayer使用了一个bool变量processedOutputBuffer来记录最终的处理结果,如果该值为true,代表该buffer中的所有数据全部写入到了audiotrack当中,那么就会对输出buffer做一个reset操作(将MediaCodecRender维护的outputBuffer置为null,同时将outputIndex置为-1),这样做的目的是下一次进入该函数时会重新发起MediaCodec.dequeueOutputBufferIndex()的操作去找一个新的可用buffer来继续往audiotrack里面送了,那要是processedOutputBuffer的处理结果是false呢?看下最开头的if就知道,下一次进来将不会去查询新的可用buffer,而是直接将本次buffer中的剩余数据继续送到audiotrack。那么这里就还剩最后一个问题,drainOutputBuffer的这个返回值对外层函数的影响到底是什么?我们看下renderer.render函数中调用处: