1.四款知名播放器比较:ijkplayer、VLCPlayer、SmartPlayer、ExoPlayer
2.Ijkplayer、ExoPlayer、VLC播放器综合比较
3.androidä¸mediamuxeråmediacodecçåºå«
四款知名播放器比较:ijkplayer、日志采集审计源码VLCPlayer、SmartPlayer、ExoPlayer
四款知名播放器各有特色,下面逐一介绍:ijkPlayer
Bilibili公司的ijkPlayer是一款开源的Android和iOS平台播放器,基于ffmpeg构建,集成简单,omv源码安装基本功能与MediaPlayer相似。然而,对于定制化需求,门槛较高。它支持软硬编解码,具备倍速播放功能,体积小巧,详情可参考官方文档。VLC Media Player
VLC是一款功能强大的跨平台播放器,支持几乎所有的多媒体文件,包括DVD、腾讯文档源码音频CD等,以及流媒体协议,对点播有良好支持。它不仅支持多种视频和音频格式,还具备丰富的输入媒体和输入格式,适合专业级使用,但集成或二次开发可能较为复杂。SmartPlayer
SmartPlayer由大牛直播SDK开发,专为安防、教育等领域设计,全平台支持RTSP和RTMP直播,点读源码性能稳定,延迟极低,资源占用低。它的优点在于直播体验上,但Windows、iOS的兼容性需单独说明。ExoPlayer
Google出品的ExoPlayer是Android平台的高级播放器,集成了MediaCodec,易于定制和扩展。它专为互联网播放优化,支持DASH和SmoothStreaming,巅峰源码出售适合开发复杂应用,但直播功能受限,仅限Android,且对软解码支持不足。 总的来说,ijkPlayer适合基础集成,VLC适合专业需求,SmartPlayer适合直播场景,而ExoPlayer则以性能和定制性见长。开发者在选择时,应考虑具体需求和平台兼容性。Ijkplayer、ExoPlayer、VLC播放器综合比较
作者:码上就说
全球知名播放器VLC Media Player,自年发布以来,因其跨平台特性备受青睐,已累计下载超过亿次,适用于Windows、Mac OS、Linux、Android和iOS等系统。其架构设计为模块化管理,由VLC main初始化libVLC并加载用户界面,libVLCcore提供模块装载和卸载机制,而access、demux和decode等功能则以模块形式存在,依赖于外部开源库。VLC的优势包括跨平台兼容、功能全面和代码解耦,但Android版本的包体积较大。
与之相比,BiliBili的开源项目IjkPlayer基于ffmpeg开发,专为Android和iOS设计。它以FFplay为核心,利用ffmpeg的扩展性,虽然开发难度相对较低,但扩展性较差,且官方维护更新较慢。ijkplayer包大小更小,大约4.2M。
Google的ExoPlayer作为轻量级播放器,以其集成的MediaCodec和良好的性能著称,尤其适合YouTube项目。它接入简单,但直播支持不足,且解码扩展性一般。ExoPlayer虽有潜力成为Android平台默认播放器,但Android 5.0后架构变化和代码整合的复杂性使其难以实现。
综合考虑,对于Android项目,推荐使用ijkplayer,因为它基于ffmpeg,支持更多协议且对直播码流切换有更好的处理。VLC虽功能全面但包大且学习成本高。直播场景推荐软件解码方式,如ijkplayer。而对包大小敏感且不介意维护频率的,VLC是个不错选择。长远看,ijkplayer适合基础定制,逐渐演变成个性化播放器。
androidä¸mediamuxeråmediacodecçåºå«
Androidä¸MediaMuxeråMediaCodecç¨ä¾
å¨Androidçå¤åªä½ç±»ä¸ï¼MediaMuxeråMediaCodecç®æ¯æ¯è¾å¹´è½»çï¼å®ä»¬æ¯JB 4.1åJB 4.3æå¼å ¥çãåè ç¨äºå°é³é¢åè§é¢è¿è¡æ··åçæå¤åªä½æ件ã缺ç¹æ¯ç®ååªè½æ¯æä¸ä¸ªaudio trackåä¸ä¸ªvideo trackï¼èä¸ä» æ¯æmp4è¾åºãä¸è¿æ¢ç¶æ¯æ°çäºç©ï¼ç¸ä¿¡ä¹åççæ¬åºè¯¥ä¼æ大çæ¹è¿ãMediaCodecç¨äºå°é³è§é¢è¿è¡å缩ç¼ç ï¼å®æ个æ¯è¾çXçå°æ¹æ¯å¯ä»¥å¯¹Surfaceå 容è¿è¡ç¼ç ï¼å¦KK 4.4ä¸å±å¹å½ååè½å°±æ¯ç¨å®å®ç°çã
注æå®ä»¬åå ¶å®ä¸äºå¤åªä½ç¸å ³ç±»çå ³ç³»ååºå«ï¼MediaExtractorç¨äºé³è§é¢åè·¯ï¼åMediaMuxeræ£å¥½æ¯åè¿ç¨ãMediaFormatç¨äºæè¿°å¤åªä½æ°æ®çæ ¼å¼ãMediaRecorderç¨äºå½å+å缩ç¼ç ï¼çæç¼ç 好çæ件å¦mp4, 3gppï¼è§é¢ä¸»è¦æ¯ç¨äºå½å¶Camera previewãMediaPlayerç¨äºææ¾å缩ç¼ç åçé³è§é¢æ件ãAudioRecordç¨äºå½å¶PCMæ°æ®ãAudioTrackç¨äºææ¾PCMæ°æ®ãPCMå³åå§é³é¢éæ ·æ°æ®ï¼å¯ä»¥ç¨å¦vlcææ¾å¨ææ¾ãå½ç¶äºï¼éééæ ·çä¹ç±»çè¦èªå·±è®¾ï¼å 为åå§éæ ·æ°æ®æ¯æ²¡ææ件头çï¼å¦ï¼
vlc --demux=rawaud --rawaud-channels 2 --rawaud-samplerate audio.pcm
åå°MediaMuxeråMediaCodecè¿ä¸¤ä¸ªç±»ï¼å®ä»¬çåèææ¡£è§/reference/android/media/MediaMuxer.htmlå/reference/android/media/MediaCodec.htmlï¼éè¾¹æ使ç¨çæ¡æ¶ãè¿ä¸ªç»åå¯ä»¥å®ç°å¾å¤åè½ï¼æ¯å¦é³è§é¢æ件çç¼è¾ï¼ç»åMediaExtractorï¼ï¼ç¨OpenGLç»å¶Surface并çæmp4æ件ï¼å±å¹å½å以å类似Camera appéçå½ååè½ï¼è½ç¶è¿ä¸ªç¨MediaRecorderæ´åéï¼çã
è¿é以ä¸ä¸ªå¾æ èçåè½ä¸ºä¾ï¼å°±æ¯å¨ä¸ä¸ªSurfaceä¸ç»å¾ç¼ç çæè§é¢ï¼åæ¶ç¨MICå½é³ç¼ç çæé³é¢ï¼ç¶åå°é³è§é¢æ··åçæmp4æ件ãç¨åºæ¬èº«æ²¡ä»ä¹ç¨ï¼ä½æ¯ç¤ºä¾äºMediaMuxeråMediaCodecçåºæ¬ç¨æ³ãæ¬ç¨åºä¸»è¦æ¯åºäºä¸¤ä¸ªæµè¯ç¨åºï¼ä¸ä¸ªæ¯Grafikaä¸çSoftInputSurfaceActivityåHWEncoderExperimentsãå®ä»¬ä¸ä¸ªæ¯çæè§é¢ï¼ä¸ä¸ªçæé³é¢ï¼è¿éæå®ä»¬ç»åä¸ä¸ï¼åæ¶çæé³é¢åè§é¢ãåºæ¬æ¡æ¶åæµç¨å¦ä¸ï¼
é¦å æ¯å½é³çº¿ç¨ï¼ä¸»è¦åèHWEncoderExperimentsãéè¿AudioRecordç±»æ¥æ¶æ¥èªéº¦å é£çéæ ·æ°æ®ï¼ç¶å丢ç»Encoderåå¤ç¼ç ï¼
AudioRecord audio_recorder;
audio_recorder = new AudioRecord(MediaRecorder.AudioSource.MIC,
SAMPLE_RATE, CHANNEL_CONFIG, AUDIO_FORMAT, buffer_size);
// ...
audio_recorder.startRecording();
while (is_recording) {
byte[] this_buffer = new byte[frame_buffer_size];
read_result = audio_recorder.read(this_buffer, 0, frame_buffer_size); // read audio raw data
// â¦
presentationTimeStamp = System.nanoTime() / ;
audioEncoder.offerAudioEncoder(this_buffer.clone(), presentationTimeStamp); // feed to audio encoder
}
è¿éä¹å¯ä»¥è®¾ç½®AudioRecordçåè°ï¼éè¿setRecordPositionUpdateListener()ï¼æ¥è§¦åé³é¢æ°æ®ç读åãofferAudioEncoder()é主è¦æ¯æaudioéæ ·æ°æ®éå ¥é³é¢MediaCodecçInputBufferè¿è¡ç¼ç ï¼
ByteBuffer[] inputBuffers = mAudioEncoder.getInputBuffers();
int inputBufferIndex = mAudioEncoder.dequeueInputBuffer(-1);
if (inputBufferIndex >= 0) {
ByteBuffer inputBuffer = inputBuffers[inputBufferIndex];
inputBuffer.clear();
inputBuffer.put(this_buffer);
...
mAudioEncoder.queueInputBuffer(inputBufferIndex, 0, this_buffer.length, presentationTimeStamp, 0);
}
ä¸é¢ï¼åèGrafika-SoftInputSurfaceActivityï¼å¹¶å å ¥é³é¢å¤çã主循ç¯å¤§ä½ååé¨åï¼
try {
// Part 1
prepareEncoder(outputFile);
...
// Part 2
for (int i = 0; i < NUM_FRAMES; i++) {
generateFrame(i);
drainVideoEncoder(false);
drainAudioEncoder(false);
}
// Part 3
...
drainVideoEncoder(true);
drainAudioEncoder(true);
} catch (IOException ioe) {
throw new RuntimeException(ioe);
} finally {
// Part 4
releaseEncoder();
}
第1é¨åæ¯åå¤å·¥ä½ï¼é¤äºvideoçMediaCodecï¼è¿éè¿åå§åäºaudioçMediaCodecï¼
MediaFormat audioFormat = new MediaFormat();
audioFormat.setInteger(MediaFormat.KEY_SAMPLE_RATE, );
audioFormat.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1);
...
mAudioEncoder = MediaCodec.createEncoderByType(AUDIO_MIME_TYPE);
mAudioEncoder.configure(audioFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
mAudioEncoder.start();
第2é¨åè¿å ¥ä¸»å¾ªç¯ï¼appå¨Surfaceä¸ç´æ¥ç»å¾ï¼ç±äºè¿ä¸ªSurfaceæ¯ä»MediaCodecä¸ç¨createInputSurface()ç³è¯·æ¥çï¼æ以ç»å®åä¸ç¨æ¾å¼ç¨queueInputBuffer()交ç»EncoderãdrainVideoEncoder()ådrainAudioEncoder()åå«å°ç¼ç 好çé³è§é¢ä»bufferä¸æ¿åºæ¥ï¼éè¿dequeueOutputBuffer()ï¼ï¼ç¶å交ç±MediaMuxerè¿è¡æ··åï¼éè¿writeSampleData()ï¼ã注æé³è§é¢éè¿PTSï¼Presentation time stampï¼å³å®äºæä¸å¸§çé³è§é¢æ°æ®ä½æ¶æ¾ç¤ºæææ¾ï¼æ¥åæ¥ï¼é³é¢çtime stampéå¨AudioRecordä»MICééå°æ°æ®æ¶è·å并æ¾å°ç¸åºçbufferInfoä¸ï¼è§é¢ç±äºæ¯å¨Surfaceä¸ç»ï¼å æ¤ç´æ¥ç¨dequeueOutputBuffer()åºæ¥çbufferInfoä¸çå°±è¡ï¼æåå°ç¼ç 好çæ°æ®éå»MediaMuxerè¿è¡å¤è·¯æ··åã
注æè¿éMuxerè¦çæaudio trackåvideo tracké½å å ¥äºåå¼å§ãMediaCodecå¨ä¸å¼å§è°ç¨dequeueOutputBuffer()æ¶ä¼è¿åä¸æ¬¡INFO_OUTPUT_FORMAT_CHANGEDæ¶æ¯ãæ们åªéå¨è¿éè·å该MediaCodecçformatï¼å¹¶æ³¨åå°MediaMuxeréãæ¥çå¤æå½åaudio trackåvideo trackæ¯å¦é½å·²å°±ç»ªï¼å¦ææ¯çè¯å°±å¯å¨Muxerã
æ»ç»æ¥è¯´ï¼drainVideoEncoder()ç主é»è¾å¤§è´å¦ä¸ï¼drainAudioEncoderä¹æ¯ç±»ä¼¼çï¼åªæ¯ævideoçMediaCodecæ¢æaudioçMediaCodecå³å¯ã
while(true) {
int encoderStatus = mVideoEncoder.dequeueOutputBuffer(mBufferInfo, TIMEOUT_USEC);
if (encoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER) {
...
} else if (encoderStatus == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
encoderOutputBuffers = mVideoEncoder.getOutputBuffers();
} else if (encoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
MediaFormat newFormat = mAudioEncoder.getOutputFormat();
mAudioTrackIndex = mMuxer.addTrack(newFormat);
mNumTracksAdded++;
if (mNumTracksAdded == TOTAL_NUM_TRACKS) {
mMuxer.start();
}
} else if (encoderStatus < 0) {
...
} else {
ByteBuffer encodedData = encoderOutputBuffers[encoderStatus];
...
if (mBufferInfo.size != 0) {
mMuxer.writeSampleData(mVideoTrackIndex, encodedData, mBufferInfo);
}
mVideoEncoder.releaseOutputBuffer(encoderStatus, false);
if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
break;
}
}
}
第3é¨åæ¯ç»æå½å¶ï¼åéEOSä¿¡æ¯ï¼è¿æ ·å¨drainVideoEncoder()ådrainAudioEncoderä¸å°±å¯ä»¥æ ¹æ®EOSéåºå 循ç¯ã第4é¨åä¸ºæ¸ çå·¥ä½ãæaudioåvideoçMediaCodecï¼MediaCodecç¨çSurfaceåMediaMuxer对象éæ¾ã
æåå ç¹æ³¨æï¼
1. å¨AndroidManifest.xmléå ä¸å½é³æéï¼å¦åå建AudioRecord对象æ¶éå®å¤±è´¥ï¼
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
2. é³è§é¢éè¿PTSåæ¥ï¼ä¸¤ä¸ªçåä½è¦ä¸è´ã
3. MediaMuxerç使ç¨è¦æç §Constructor -> addTrack -> start -> writeSampleData -> stop ç顺åºãå¦ææ¢æé³é¢åæè§é¢ï¼å¨stopå两个é½è¦writeSampleData()è¿ã
Code referencesï¼
Grafika: /google/grafika
Bigflake: /mediacodec/
HWEncoderExperimentsï¼/OnlyInAmerica/HWEncoderExperiments/tree/audioonly/HWEncoderExperiments/src/main/java/net/openwatch/hwencoderexperiments
Android testï¼/4.4.2_r2/xref/cts/tests/tests/media/src/android/media/cts/
/4.4.2_r2/xref/pdk/apps/TestingCamera2/src/com/android/testingcamera2/CameraRecordingStream.java