一个被标记为图片的“视频”可以播放吗

3,406 阅读14分钟

背景介绍

图片、视频、文本是我们日常生活中常见的形态,它们之所以被认为是图片、视频、文本,就是因为它们按照图片、视频、文本的封装规则去组装的。我们在接收一个文件的时候,不知道它们是图片、视频还是文本,但是我们会按照特定的规则是解析它们,如果能够解析成功,说明它们就是符合这样的规则的,我们就认定它们是图片、视频或者文本。

网络上很多文件类型,如果播放一个视频,你以为的是视频格式,但是给你的确实图片,你还用视频的解析规则是识别,那最终还能识别成功吗? 现在给一个网络链接:m.wdy5.com/vod/play/34… 其中视频的播放链接是:jx.shxcg.cn:4434/url/api/cur…

起始根据url的解析规则我们知道最终的播放url就是:zyxin.shxcg.cn:4434/upload/%E8%…

这个视频能不能播放呢?

我们使用ffprobe去探测一下:

[hls,applehttp @ 0x55647e5cb080] Opening 'https://sf1-ttcdn-tos.pstatp.com/obj/web.business.image/202104255d0de08d7b4d1ae04d66becc' for reading
[hls,applehttp @ 0x55647e5cb080] Could not find codec parameters for stream 0 (Video: png, none(pc)): unspecified size
Consider increasing the value for the 'analyzeduration' and 'probesize' options
Input #0, hls,applehttp, from 'https://zyxin.shxcg.cn:4434/upload/%E8%89%B2%E6%88%92.2007.BD1080p.m3u8':
  Duration: 02:38:15.07, bitrate: 0 kb/s
  Program 0
    Metadata:
      variant_bitrate : 0
    Stream #0:0: Video: png, none(pc), 25 tbr, 25 tbn, 25 tbc
    Metadata:
      variant_bitrate : 0

这个M3U8视频中只有一个视频流信息,而且这个视频流信息竟然是png格式,确实令人匪夷所思。如果真是这种格式肯定会播放失败的。

问题初探

我们再深入一点,既然是一个M3U8文件,那我们分析一下具体的分片文件吧。这是M3U8文件的第一个分片:sf1-ttcdn-tos.pstatp.com/obj/web.bus…

从这个url来看,似乎服务器有意让你将这个url识别为图片文件,很奇怪吧。

干脆我将这个文件下载下来。下面的video_0.ts就是这个文件。

我先用EasyICE识别一下,EasyICE是一个TS分析的工具,可以分析TS文件的基本信息和码流信息,很有帮助。

test1.png

test2.png

test3.png

用这个识别工具探测发现这个文件确实是视频文件啊,视频流和音频流的信息能够识别的清清楚楚。

不死心。直接打开这个video_0.ts看看二进制文件信息吧。

test4.png

发现这个文件的头部直接使用PNG的头部封装的,也就是说,服务器直接想让你识别成功PNG的格式,我们先不管服务器为什么要这么做,现在我们已经知道了根本原因,就是服务器下发了一个"PNG图片",然后我们当成了“视频”,最后我们还没有识别成功,那能不能播放成功了?

我们现在的逻辑是使用ExoPlayer和Ijkplayer协同使用,互相辅助补充,共同解决播放问题,通过这样的协同,可以最大程度的解决播放失败的问题。

我直接去掉PNG头部信息,然后再利用ffprobe解析,得到的结果如下:

Input #0, mpegts, from 'error.ts':
  Duration: 00:00:10.43, start: 1.566833, bitrate: 3281 kb/s
  Program 1
    Metadata:
      service_name    : ?domp4电影
      service_provider: FFmpeg
    Stream #0:0[0x100]: Video: h264 (High) ([27][0][0][0] / 0x001B), yuv420p(progressive), 1920x1080 [SAR 1:1 DAR 16:9], 23.98 fps, 23.98 tbr, 90k tbn, 47.95 tbc
    Stream #0:1[0x101](und): Audio: aac (HE-AAC) ([15][0][0][0] / 0x000F), 48000 Hz, 7.1, fltp, 255 kb/s

ExoPlayer播放失败剖析

我们将jx.shxcg.cn:4434/url/api/cur… 放入ExoPlayer中,验证是否能够播放?

第一次尝试

结果是报错:

2021-05-20 10:07:19.756 17835-19205/com.jeffmony.videodemo E/playersdk.ExoPlayerImplInternal: Source error.
    com.google.android.exoplayer2.source.UnrecognizedInputFormatException: None of the available extractors (Mp4Extractor, FragmentedMp4Extractor, TsExtractor, FlvExtractor, Mp3Extractor, MatroskaExtractor, AdtsExtractor, Ac3Extractor, OggExtractor, PsExtractor, WavExtractor, AmrExtractor) could read the stream.
        at com.google.android.exoplayer2.source.ExtractorMediaPeriod$ExtractorHolder.selectExtractor(ExtractorMediaPeriod.java:1041)
        at com.google.android.exoplayer2.source.ExtractorMediaPeriod$ExtractingLoadable.load(ExtractorMediaPeriod.java:949)
        at com.google.android.exoplayer2.upstream.Loader$LoadTask.run(Loader.java:324)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
        at java.lang.Thread.run(Thread.java:919)
2021-05-20 10:07:19.757 17835-17835/com.jeffmony.videodemo I/playersdk.ExoPlayerImpl: onPlayerError, error = com.google.android.exoplayer2.ExoPlaybackException, cause: com.google.android.exoplayer2.source.UnrecognizedInputFormatException: None of the available extractors (Mp4Extractor, FragmentedMp4Extractor, TsExtractor, FlvExtractor, Mp3Extractor, MatroskaExtractor, AdtsExtractor, Ac3Extractor, OggExtractor, PsExtractor, WavExtractor, AmrExtractor) could read the stream.
2021-05-20 10:07:19.757 17835-17835/com.jeffmony.videodemo E/playersdk.ExoPlayerImpl: isUnrecognizedInputFormat = mContentType =

我们分析一下playersdk的逻辑,发现我们视频M3U8类型的逻辑有点问题。

exoplayer中Util.java中判断是不是M3U8类型的规则如下:

/**
 * Makes a best guess to infer the type from a file name.
 *
 * @param fileName Name of the file. It can include the path of the file.
 * @return The content type.
 */
@C.ContentType
public static int inferContentType(String fileName) {
  fileName = Util.toLowerInvariant(fileName);
  if (fileName.endsWith(".mpd")) {
    return C.TYPE_DASH;
  } else if (fileName.endsWith(".m3u8")) {
    return C.TYPE_HLS;
  } else if (fileName.matches(".*\\.ism(l)?(/manifest(\\(.+\\))?)?")) {
    return C.TYPE_SS;
  } else {
    return C.TYPE_OTHER;
  }
}

也就是说当前url的fileName后缀是不是.m3u8来判断是不是M3U8类型。jx.shxcg.cn:4434/url/api/cur… 的fileName是curl.php,显然和M3U8没有任何关系。

这样判断M3U8类型是不是符合规则的?

我们先按下不表示,我们看一下Android系统中是如何判断M3U8类型的。

MediaPlayer中识别是不是M3U8类型的判断在/frameworks/av/media/libmediaplayerservice/MediaPlayerFactory.cpp文件中:

172    virtual float scoreFactory(const sp<IMediaPlayer>& /*client*/,
173                               const char* url,
174                               float curScore) {
175        static const float kOurScore = 0.8;
176
177        if (kOurScore <= curScore)
178            return 0.0;
179
180        if (!strncasecmp("http://", url, 7)
181                || !strncasecmp("https://", url, 8)
182                || !strncasecmp("file://", url, 7)) {
183            size_t len = strlen(url);
184            if (len >= 5 && !strcasecmp(".m3u8", &url[len - 5])) {
185                return kOurScore;
186            }
187
188            if (strstr(url,"m3u8")) {
189                return kOurScore;
190            }
191
192            if ((len >= 4 && !strcasecmp(".sdp", &url[len - 4])) || strstr(url, ".sdp?")) {
193                return kOurScore;
194            }
195        }
196
197        if (!strncasecmp("rtsp://", url, 7)) {
198            return kOurScore;
199        }
200
201        return 0.0;
202    }

在188行可以看到只有url中包含m3u8就认为这是一个m3u8类型,大家可能会说这样的判断是不是太草率了,系统代码也能这么写?标准就是标准,你既然拿到了播放器中播放,我们就要尽可能放大条件。

ExoPlayer中的判断条件太紧了,导致第一关就判断失败了。我们还是按照标准放宽条件。

第二次尝试

修改ExoPlayer中代码,让jx.shxcg.cn:4434/url/api/cur… 被识别成为M3U8类型。

然后再去第二次尝试。还是出现报错:

2021-05-20 10:25:24.365 17835-20242/com.jeffmony.videodemo E/playersdk.ExoPlayerImplInternal: Internal runtime error.
    java.lang.IllegalStateException
        at android.media.MediaCodec.native_queueInputBuffer(Native Method)
        at android.media.MediaCodec.queueInputBuffer(MediaCodec.java:2450)
        at com.google.android.exoplayer2.mediacodec.MediaCodecRenderer.feedInputBuffer(MediaCodecRenderer.java:780)
        at com.google.android.exoplayer2.mediacodec.MediaCodecRenderer.render(MediaCodecRenderer.java:590)
        at com.google.android.exoplayer2.ExoPlayerImplInternal.doSomeWork(ExoPlayerImplInternal.java:521)
        at com.google.android.exoplayer2.ExoPlayerImplInternal.handleMessage(ExoPlayerImplInternal.java:300)
        at android.os.Handler.dispatchMessage(Handler.java:103)
        at android.os.Looper.loop(Looper.java:230)
        at android.os.HandlerThread.run(HandlerThread.java:67)
2021-05-20 10:25:24.397 17835-20242/com.jeffmony.videodemo E/playersdk.ExoPlayerImplInternal: Stop failed.
    java.lang.IllegalStateException
        at android.media.MediaCodec.native_stop(Native Method)
        at android.media.MediaCodec.stop(MediaCodec.java:2147)
        at com.google.android.exoplayer2.mediacodec.MediaCodecRenderer.releaseCodec(MediaCodecRenderer.java:529)
        at com.googler.android.exoplayer2.mediacodec.MediaCodecRenderer.onDisabled(MediaCodecRenderer.java:485)
        at com.google.android.exoplayer2.audio.MediaCodecAudioRenderer.onDisabled(MediaCodecAudioRenderer.java:480)
        at com.google.android.exoplayer2.BaseRenderer.disable(BaseRenderer.java:153)
        at com.google.android.exoplayer2.ExoPlayerImplInternal.disableRenderer(ExoPlayerImplInternal.java:1006)
        at com.google.android.exoplayer2.ExoPlayerImplInternal.resetInternal(ExoPlayerImplInternal.java:773)
        at com.google.android.exoplayer2.ExoPlayerImplInternal.stopInternal(ExoPlayerImplInternal.java:734)
        at com.google.android.exoplayer2.ExoPlayerImplInternal.handleMessage(ExoPlayerImplInternal.java:352)
        at android.os.Handler.dispatchMessage(Handler.java:103)
        at android.os.Looper.loop(Looper.java:230)
        at android.os.HandlerThread.run(HandlerThread.java:67)

看到上面的报错大家是不是一脸懵逼,怎么就codec异常了,什么额外的信息都没有,这时候不要慌,先看看系统的log,既然是codec异常吗,系统的ACodec肯定会有一些额外的信息的。

2021-05-20 10:25:24.291 17835-20266/com.jeffmony.videodemo I/ACodec: [OMX.qcom.video.decoder.avc]configureCodec AMessage(what = 'conf', target = 1) = {
      Buffer csd-1 = {
        00000000:  00 00 01 68 eb 8f 2c                              ...h..,
      }
      int32_t max-height = 1080
      int32_t max-width = 1920
      string mime = "video/avc"
      int32_t width = 1920
      int32_t priority = 0
      int32_t rotation-degrees = 0
      int32_t max-input-size = 1566720
      int32_t height = 1080
      Buffer csd-0 = {
        00000000:  00 00 01 67 64 00 28 ac  2c a5 01 e0 08 9f 97 01  ...gd.(.,.......
        00000010:  10 00 00 3e 90 00 0b b8  0e 00 00 03 00 f4 24 00  ...>..........$.
        00000020:  00 1e 84 82 ef 2e 0a 00                           ........
      }
      RefBase *native-window = 0x7b0521f000
    }
 
 
 
2021-05-20 10:25:24.345 17835-20271/com.jeffmony.videodemo I/ACodec: [OMX.google.aac.decoder]configureCodec AMessage(what = 'conf', target = 4) = {
      int32_t sample-rate = 24000
      string mime = "audio/mp4a-latm"
      int32_t channel-count = 0
      int32_t priority = 0
      Buffer csd-0 = {
        00000000:  13 00                                             ..
      }
    }
2021-05-20 10:25:24.345 17835-20271/com.jeffmony.videodemo E/ACodec: setupRawAudioFormat: incorrect numChannels: 0
2021-05-20 10:25:24.346 1489-28983/? E/OMXNodeInstance: setConfig(0xed20a900:google.aac.decoder, ConfigPriority(0x6f800002)) ERROR: Undefined(0x80001001)
2021-05-20 10:25:24.346 17835-20271/com.jeffmony.videodemo I/ACodec: codec does not support config priority (err -2147483648)
2021-05-20 10:25:24.346 1489-1569/? E/OMXNodeInstance: getConfig(0xed20a900:google.aac.decoder, ConfigAndroidVendorExtension(0x6f100004)) ERROR: Undefined(0x80001001)
2021-05-20 10:25:24.347 17835-20271/com.jeffmony.videodemo W/ExtendedACodec: Failed to get extension for extradata parameter
2021-05-20 10:25:24.349 890-989/? D/DispSync: getRefreshRate 0
2021-05-20 10:25:24.359 17835-17835/com.jeffmony.videodemo I/playersdk.ExoPlayerImpl: PlayerState ---> AudioDecodeStart
2021-05-20 10:25:24.360 1489-20272/? W/SoftAAC2: aacDecoder_ConfigRaw decoderErr = 0x0005
2021-05-20 10:25:24.360 17835-20271/com.jeffmony.videodemo E/ACodec: [OMX.google.aac.decoder] ERROR(0x80001001)
2021-05-20 10:25:24.361 17835-20271/com.jeffmony.videodemo E/ACodec: signalError(omxError 0x80001001, internalError -2147483648)
2021-05-20 10:25:24.361 17835-20271/com.jeffmony.videodemo E/MediaCodec: Codec reported err 0x80001001, actionCode 0, while in state 6

视频流识别出来没有什么问题,但是音频流识别出来问题就大了,channel-count为0,sample-rate 竟然是24000,和我们之前用EasyICE软件识别出来的差距比较大。大家可以向上找一下,看看之前识别出来的EasyICE是什么结果。

而且系统也报错了:setupRawAudioFormat: incorrect numChannels: 0,说明这种情况下channel-count为0也是有问题的,说明ExoPlayer这儿的识别是有问题的。

接下来就要跟踪ExoPlayer系统源码了,Debug跟着系统的源码一步步走,发现最终识别Audio信息地方在AdtsReader.java中的parseAdtsHeader方法中。

/**
 * Parses the sample header.
 */
private void parseAdtsHeader() throws ParserException {
  adtsScratch.setPosition(0);
 
  if (!hasOutputFormat) {
    int audioObjectType = adtsScratch.readBits(2) + 1;
    if (audioObjectType != 2) {
      // The stream indicates AAC-Main (1), AAC-SSR (3) or AAC-LTP (4). When the stream indicates
      // AAC-Main it's more likely that the stream contains HE-AAC (5), which cannot be
      // represented correctly in the 2 bit audio_object_type field in the ADTS header. In
      // practice when the stream indicates AAC-SSR or AAC-LTP it more commonly contains AAC-LC or
      // HE-AAC. Since most Android devices don't support AAC-Main, AAC-SSR or AAC-LTP, and since
      // indicating AAC-LC works for HE-AAC streams, we pretend that we're dealing with AAC-LC and
      // hope for the best. In practice this often works.
      // See: https://github.com/google/ExoPlayer/issues/774
      // See: https://github.com/google/ExoPlayer/issues/1383
      LogEx.w(TAG, "Detected audio object type: " + audioObjectType + ", but assuming AAC LC.");
      audioObjectType = 2;
    }
 
    int sampleRateIndex = adtsScratch.readBits(4);
    adtsScratch.skipBits(1);
    int channelConfig = adtsScratch.readBits(3);
 
    byte[] audioSpecificConfig = CodecSpecificDataUtil.buildAacAudioSpecificConfig(
        audioObjectType, sampleRateIndex, channelConfig);
    Pair<Integer, Integer> audioParams = CodecSpecificDataUtil.parseAacAudioSpecificConfig(
        audioSpecificConfig);
 
    Format format = Format.createAudioSampleFormat(formatId, MimeTypes.AUDIO_AAC, null,
        Format.NO_VALUE, Format.NO_VALUE, audioParams.second, audioParams.first,
        Collections.singletonList(audioSpecificConfig), null, 0, language);
    // In this class a sample is an access unit, but the MediaFormat sample rate specifies the
    // number of PCM audio samples per second.
    sampleDurationUs = (C.MICROS_PER_SECOND * 1024) / format.sampleRate;
    output.format(format);
    hasOutputFormat = true;
  } else {
    adtsScratch.skipBits(10);
  }
 
  adtsScratch.skipBits(4);
  int sampleSize = adtsScratch.readBits(13) - 2 /* the sync word */ - HEADER_SIZE;
  if (hasCrc) {
    sampleSize -= CRC_SIZE;
  }
 
  setReadingSampleState(output, sampleDurationUs, 0, sampleSize);
}

这个方法的主要作用是识别Atds格式的头部信息,头部信息中有channel-count和sample-rate信息,但是audioObjectType识别是有问题的,为什么总是2,我对源码的这儿是有存疑的。


int audioObjectType = adtsScratch.readBits(2) + 1;
if (audioObjectType != 2) {
  // The stream indicates AAC-Main (1), AAC-SSR (3) or AAC-LTP (4). When the stream indicates
  // AAC-Main it's more likely that the stream contains HE-AAC (5), which cannot be
  // represented correctly in the 2 bit audio_object_type field in the ADTS header. In
  // practice when the stream indicates AAC-SSR or AAC-LTP it more commonly contains AAC-LC or
  // HE-AAC. Since most Android devices don't support AAC-Main, AAC-SSR or AAC-LTP, and since
  // indicating AAC-LC works for HE-AAC streams, we pretend that we're dealing with AAC-LC and
  // hope for the best. In practice this often works.
  // See: https://github.com/google/ExoPlayer/issues/774
  // See: https://github.com/google/ExoPlayer/issues/1383
  LogEx.w(TAG, "Detected audio object type: " + audioObjectType + ", but assuming AAC LC.");
  audioObjectType = 2;
}

那应该是多少,回到“问题初探”章节,audio的类型应该是AAC-HE类型,什么类型都直接赋给2我觉得有点欠妥。但还是要深入分析一下,还好真的不是我一个人遇到这样的问题。

找一下ExoPlayer之前的issue:github.com/google/ExoP… ,这个issue中最后提出的一个方法是采用FFmpeg拓展来解决识别audio 7.1的识别问题。很显然google的开发者也没有找到很好的修复方案。

这一块需要我们努力加油了,如果能解决现有的ExoPlayer问题,成为一个ExoPlayer committer,也是很棒的体验,给我们提一个要求哈。

IjkPlayer播放失败剖析

既然ExoPlayer的实现难度比较大,那我们还是使用IjkPlayer去补充解决这个问题。下面分析一下IjkPlayer是否能播放jx.shxcg.cn:4434/url/api/cur…

使用IjkPlayer播放视频的视频报如下的问题:


2021-05-20 10:59:40.968 21218-21313/com.jeffmony.playerdemo W/IJKMEDIA: Could not find codec parameters for stream 0 (Video: png, none(pc)): unspecified size
    Consider increasing the value for the 'analyzeduration' and 'probesize' options
2021-05-20 10:59:40.968 21218-21313/com.jeffmony.playerdemo I/IJKMEDIA: max_frame_duration: 10.000
2021-05-20 10:59:40.968 21218-21313/com.jeffmony.playerdemo I/IJKMEDIA: Input #0, hls,applehttp, from 'https://jx.shxcg.cn:4434/url/api/curl.php?url=https://zyxin.shxcg.cn:4434/upload/%E8%89%B2%E6%88%92.2007.BD1080p.m3u8':
2021-05-20 10:59:40.968 21218-21313/com.jeffmony.playerdemo I/IJKMEDIA:   Duration:
2021-05-20 10:59:40.968 21218-21313/com.jeffmony.playerdemo I/IJKMEDIA: 02:38:15.07
2021-05-20 10:59:40.968 21218-21313/com.jeffmony.playerdemo I/IJKMEDIA: , bitrate:
2021-05-20 10:59:40.968 21218-21313/com.jeffmony.playerdemo I/IJKMEDIA: N/A
2021-05-20 10:59:40.968 21218-21313/com.jeffmony.playerdemo I/IJKMEDIA:   Program 0
2021-05-20 10:59:40.968 21218-21313/com.jeffmony.playerdemo I/IJKMEDIA:     Metadata:
2021-05-20 10:59:40.968 21218-21313/com.jeffmony.playerdemo I/IJKMEDIA:       variant_bitrate :
2021-05-20 10:59:40.968 21218-21313/com.jeffmony.playerdemo I/IJKMEDIA: 0
2021-05-20 10:59:40.968 21218-21313/com.jeffmony.playerdemo I/IJKMEDIA:     Stream #0:0
2021-05-20 10:59:40.968 21218-21313/com.jeffmony.playerdemo I/IJKMEDIA: : Video: png, none(pc)
2021-05-20 10:59:40.968 21218-21313/com.jeffmony.playerdemo I/IJKMEDIA: ,
2021-05-20 10:59:40.968 21218-21313/com.jeffmony.playerdemo I/IJKMEDIA: 25 tbr,
2021-05-20 10:59:40.968 21218-21313/com.jeffmony.playerdemo I/IJKMEDIA: 25 tbn,
2021-05-20 10:59:40.968 21218-21313/com.jeffmony.playerdemo I/IJKMEDIA: 25 tbc
2021-05-20 10:59:40.968 21218-21313/com.jeffmony.playerdemo I/IJKMEDIA:     Metadata:
2021-05-20 10:59:40.968 21218-21313/com.jeffmony.playerdemo I/IJKMEDIA:       variant_bitrate :
2021-05-20 10:59:40.968 21218-21313/com.jeffmony.playerdemo I/IJKMEDIA: 0
2021-05-20 10:59:40.969 21218-21313/com.jeffmony.playerdemo I/IJKMEDIA: VideoCodec: avcodec, png
2021-05-20 10:59:40.969 21218-21313/com.jeffmony.playerdemo W/IJKMEDIA: fps: 25.000000 (normal)

这个问题乍看一下还以为是ffmpeg没有接入png类型格式了,通过我们上面对ExoPlayer的分析,我们刚开始就能确定,肯定是识别分片的格式出现问题。

经过一顿操作,找到IjkPlayer中识别视频类型的地方:./android/contrib/ffmpeg-armv7a/libavformat/format.c中的av_probe_input_format3函数。


AVInputFormat *av_probe_input_format3(AVProbeData *pd, int is_opened,
                                      int *score_ret)
{
    AVProbeData lpd = *pd;
    const AVInputFormat *fmt1 = NULL;
    AVInputFormat *fmt = NULL;
    int score, score_max = 0;
    void *i = 0;
    const static uint8_t zerobuffer[AVPROBE_PADDING_SIZE];
    enum nodat {
        NO_ID3,
        ID3_ALMOST_GREATER_PROBE,
        ID3_GREATER_PROBE,
        ID3_GREATER_MAX_PROBE,
    } nodat = NO_ID3;
 
    if (!lpd.buf)
        lpd.buf = (unsigned char *) zerobuffer;
 
    if (lpd.buf_size > 10 && ff_id3v2_match(lpd.buf, ID3v2_DEFAULT_MAGIC)) {
        int id3len = ff_id3v2_tag_len(lpd.buf);
        if (lpd.buf_size > id3len + 16) {
            if (lpd.buf_size < 2LL*id3len + 16)
                nodat = ID3_ALMOST_GREATER_PROBE;
            lpd.buf      += id3len;
            lpd.buf_size -= id3len;
        } else if (id3len >= PROBE_BUF_MAX) {
            nodat = ID3_GREATER_MAX_PROBE;
        } else
            nodat = ID3_GREATER_PROBE;
    }
 
    while ((fmt1 = av_demuxer_iterate(&i))) {
        if (!is_opened == !(fmt1->flags & AVFMT_NOFILE) && strcmp(fmt1->name, "image2"))
            continue;
        score = 0;
        if (fmt1->read_probe) {
            score = fmt1->read_probe(&lpd);
            if (score)
                av_log(NULL, AV_LOG_TRACE, "Probing %s score:%d size:%d\n", fmt1->name, score, lpd.buf_size);
            if (fmt1->extensions && av_match_ext(lpd.filename, fmt1->extensions)) {
                switch (nodat) {
                case NO_ID3:
                    score = FFMAX(score, 1);
                    break;
                case ID3_GREATER_PROBE:
                case ID3_ALMOST_GREATER_PROBE:
                    score = FFMAX(score, AVPROBE_SCORE_EXTENSION / 2 - 1);
                    break;
                case ID3_GREATER_MAX_PROBE:
                    score = FFMAX(score, AVPROBE_SCORE_EXTENSION);
                    break;
                }
            }
        } else if (fmt1->extensions) {
            if (av_match_ext(lpd.filename, fmt1->extensions))
                score = AVPROBE_SCORE_EXTENSION;
        }
        if (av_match_name(lpd.mime_type, fmt1->mime_type)) {
            if (AVPROBE_SCORE_MIME > score) {
                av_log(NULL, AV_LOG_DEBUG, "Probing %s score:%d increased to %d due to MIME type\n", fmt1->name, score, AVPROBE_SCORE_MIME);
                score = AVPROBE_SCORE_MIME;
            }
        }
        if (score > score_max) {
            score_max = score;
            fmt       = (AVInputFormat*)fmt1;
        } else if (score == score_max)
            fmt = NULL;
    }
    if (nodat == ID3_GREATER_PROBE)
        score_max = FFMIN(AVPROBE_SCORE_EXTENSION / 2 - 1, score_max);
    *score_ret = score_max;
 
    return fmt;
}

我们这儿简单介绍一下IjkPlayer中是如何得到视频真正的封装格式的。在一个while循环中,将当前的视频数据放入,经过每种封装格式计算出一个score, score越大说明和当前的封装格式越匹配。

很显然,如果被识别为png格式,肯定是通过png得到的score最大。

但是我们播放的是视频,针对图片格式,不应该播放图片类型,其实我们在IjkPlayer中有很多剔除image的代码,所以得到最保险的方式应该是剔除png类型。

下面是我修复之后的代码:

if (score > score_max) {
    if (!strcmp(fmt1->name, "png_pipe")) continue;
    score_max = score;
    fmt       = (AVInputFormat*)fmt1;
} else if (score == score_max)
    fmt = NULL;

加了一行代码:if (!strcmp(fmt1->name, "png_pipe")) continue;

思考

  • 发现问题-->分析问题--->解决问题,其中比较重要的环节是“分析问题”,分析问题务求要分析透彻,从根本的角度解析问题,然后找到影响最小的解决方案。
  • 能够在ExoPlayer上解决这个问题,给大家留下一个思考,希望集合打击的智慧在ExoPlayer上解决这个问题。