Android FFmpeg音視頻解碼播放示例詳解
前言
看到很多都對音視頻這塊非常的感興趣,接下來就長篇贅述一下音視頻的前前后后,應(yīng)該從明天開始從音視頻的初中高三個(gè)層次展開淺談??
本文分別為:
- FFmpeg簡介
- FFmpeg音視頻解碼播放
- Clang編譯FFmpeg常見問題
今天來說一下關(guān)于今天先說一下FFmpeg一些內(nèi)容
內(nèi)容如下:
1.2022最新Android11位大廠面試專題,128道附答案
2.音視頻大合集,從初中高到面試應(yīng)有盡有
3.Android車載應(yīng)用大合集,從零開始一起學(xué)
4.性能優(yōu)化大合集,告別優(yōu)化煩惱
5.Framework大合集,從里到外分析的明明白白
6.Flutter大合集,進(jìn)階Flutter高級工程師
7.compose大合集,擁抱新技術(shù)
8.Jetpack大合集,全家桶一次吃個(gè)夠
9.架構(gòu)大合集,輕松應(yīng)對工作需求
10.Android基礎(chǔ)篇大合集,根基穩(wěn)固高樓平地起
整理不易,關(guān)注一下吧。開始進(jìn)入正題,?( ´???` ) ??
一丶FFmpeg簡介
1.簡介
FFmpeg(FastForward Mpeg)是一款遵循GPL的開源軟件,在音視頻處理方面表現(xiàn)十分優(yōu)秀,幾乎囊括了現(xiàn)存所有的視音頻格式的編碼,解碼、轉(zhuǎn)碼、混合、過濾及播放。同時(shí)也是一款跨平臺(tái)的軟件,完美兼容Linux、Windows、Mac OSX等平臺(tái)。其實(shí)它由3大部件組成:
FFmpeg:由命令行組成,用于多媒體格式轉(zhuǎn)換 FFplay:基于FFmpeg開源代碼庫libraries做的多媒體播放器 FFprobe:基于FFmpeg做的多媒體流分析器
2.FFmpeg兩個(gè)強(qiáng)大功能
1.命令功能
2.api功能
2.1 命令功能
應(yīng)用程序使用方法
上圖實(shí)例是將一個(gè)bus.avi中視頻分離出來,利用三個(gè)應(yīng)用程序中的ffmpeg.exe
。
ffmpeg –y –i input –vcodeccopy –an output.avi
其中-y表示覆蓋同名文件,-i表示輸入文件即bus.avi,-vcodec表示編碼方式,后面的copy表示用原來的編碼方式,即不重新編碼,-an表示去除音頻,后面的busv.avi表示分離出的視頻文件。
同理將視頻中的音頻文件分離出來的命令行為:
ffmpeg -ibus.avi -acodec copy -vn busa.wav
上面舉例說明了應(yīng)用程序的用法,應(yīng)用程序的命令行相對代碼要簡單很多,也能實(shí)現(xiàn)例如音視頻分離、轉(zhuǎn)碼、播放等各種功能,如視頻轉(zhuǎn)碼的命令行為:
ffmpeg -y -i input.mp4 -vcodec libx264 -acodec copy output.mp4
這個(gè)命令用于剪切視頻,-ss表示從第幾秒開始,如上實(shí)例為從第5秒開始,-t代表剪持續(xù)幾秒長度的視頻,如上實(shí)例就是剪10秒長度的視頻,copy表示視頻編碼格式和音頻編碼格式與原視頻統(tǒng)一。
ffmpeg -ss 0:0:5 -t 0:0:10 -i input.avi -vcodec copy -acodec copy output.avi
分離視頻音頻流
ffmpeg -i input_file -vcodec copy -an output_file_video //分離視頻流 ffmpeg -i input_file -acodec copy -vn output_file_audio //分離音頻流
視頻解復(fù)用
ffmpeg –i test.mp4 –vcodec copy –an –f m4v test.264 ffmpeg –i test.avi –vcodec copy –an –f m4v test.264
視頻轉(zhuǎn)碼
ffmpeg –i test.mp4 –vcodec h264 –s 352*278 –an –f m4v test.264 //轉(zhuǎn)碼為碼流原始文件 ffmpeg –i test.mp4 –vcodec h264 –bf 0 –g 25 –s 352*278 –an –f m4v test.264 //轉(zhuǎn)碼為碼流原始文件 ffmpeg –i test.avi -vcodec mpeg4 –vtag xvid –qsame test_xvid.avi //轉(zhuǎn)碼為封裝文件
視頻封裝
ffmpeg –i video_file –i audio_file –vcodec copy –acodec copy output_file
視頻剪切
ffmpeg –i test.avi –r 1 –f image2 image-%3d.jpeg //提取圖片 ffmpeg -ss 0:1:30 -t 0:0:20 -i input.avi -vcodec copy -acodec copy output.avi //剪切視頻 //-r 提取圖像的頻率,-ss 開始時(shí)間,-t 持續(xù)時(shí)間
視頻錄制
ffmpeg –i rtsp://192.168.3.205:5555/test –vcodec copy out.avi
YUV序列播放
ffplay -f rawvideo -video_size 1920x1080 input.yuv
YUV序列轉(zhuǎn)AVI
ffmpeg –s w*h –pix_fmt yuv420p –i input.yuv –vcodec mpeg4 output.avi
常用參數(shù)說明:
主要參數(shù):
- -i 設(shè)定輸入流
- -f 設(shè)定輸出格式
- -ss 開始時(shí)間 視頻參數(shù):
- -b 設(shè)定視頻流量,默認(rèn)為200Kbit/s
- -r 設(shè)定幀速率,默認(rèn)為25
- -s 設(shè)定畫面的寬與高 -aspect 設(shè)定畫面的比例
- -vn 不處理視頻
- -vcodec 設(shè)定視頻編解碼器,未設(shè)定時(shí)則使用與輸入流相同的編解碼器 音頻參數(shù):
- -ar 設(shè)定采樣率 -ac 設(shè)定聲音的Channel數(shù)
- -acodec 設(shè)定聲音編解碼器,未設(shè)定時(shí)則使用與輸入流相同的編解碼器 -an 不處理音頻
二丶FFmpeg音視頻解碼播放
前言
通常情況下,媒體文件以如MP4,MKV、FLV等等格式存在我們的計(jì)算機(jī),手機(jī)等設(shè)備中,而這些文件格式都屬于封裝格式,就是把音視頻數(shù)據(jù)按照相應(yīng)的規(guī)范,打包成文件。
1.FFmpeg 音視頻解碼流程
平常我們播放媒體文件時(shí),通常需要經(jīng)過以下幾個(gè)步驟
2.FFmpeg 音視頻解碼原理
2.1.解協(xié)議
將流媒體協(xié)議的數(shù)據(jù),解析為標(biāo)準(zhǔn)的相應(yīng)的封裝格式數(shù)據(jù)。視音頻在網(wǎng)絡(luò)上傳播的時(shí)候,常常采用各種流媒體協(xié)議,例如 HTTP,RTMP,或是 MMS 等等。這些協(xié)議在傳輸視音頻數(shù)據(jù)的同時(shí),也會(huì)傳輸一些信令數(shù)據(jù)。這些信令數(shù)據(jù)包括對播放的控制(播放,暫停,停止),或者對網(wǎng)絡(luò)狀態(tài)的描述等。
解協(xié)議的過程中會(huì)去除掉信令數(shù)據(jù)而只保留視音頻數(shù)據(jù)。例如,采用 RTMP 協(xié)議傳輸?shù)臄?shù)據(jù),經(jīng)過解協(xié)議操作后,輸出 FLV 格式的數(shù)據(jù)。
2.2.解封裝
將輸入的封裝格式的數(shù)據(jù),分離成為音頻流壓縮編碼數(shù)據(jù)和視頻流壓縮編碼數(shù)據(jù)。封裝格式種類很多,例如 MP4,MKV,RMVB,TS,F(xiàn)LV,AVI 等等,它的作用就是將已經(jīng)壓縮編碼的視頻數(shù)據(jù)和音頻數(shù)據(jù)按照一定的格式放到一起。例如,F(xiàn)LV 格式的數(shù)據(jù),經(jīng)過解封裝操作后,輸出 H.264 編碼的視頻碼流和AAC 編碼的音頻碼流。
2.3.解碼
將視頻/音頻壓縮編碼數(shù)據(jù),解碼成為非壓縮的視頻/音頻原始數(shù)據(jù)。解碼是整個(gè)系統(tǒng)中最重要也是最復(fù)雜的一個(gè)環(huán)節(jié)。通過解碼,壓縮編碼的視頻數(shù)據(jù)輸出成為非壓縮的顏色數(shù)據(jù),例如 YUV420P,RGB 等等;
2.4.音視頻同步
根據(jù)解封裝模塊處理過程中獲取到的參數(shù)信息,同步解碼出來的視頻和音頻數(shù)據(jù),并將視頻音頻數(shù)據(jù)送至系統(tǒng)的顯卡和聲卡播放出來。
2.5.FFmpeg音視頻解碼
通過前文,我們知道每一個(gè)媒體文件在被終端播放前主要經(jīng)過了兩個(gè)關(guān)鍵步驟,分別是解封裝和解碼。而在ffmpeg中,使用相關(guān)接口實(shí)現(xiàn)解封裝和解碼流程如下圖:
由上圖可知,我們需要重點(diǎn)關(guān)注下面這些FFmpeg的API接口:
av_register_all()
:注冊所有組件。avformat_open_input()
:打開輸入視頻文件。avformat_find_stream_info()
:獲取視頻文件信息。avcodec_find_decoder()
:查找解碼器。avcodec_open2()
:打開解碼器。av_read_frame()
:從輸入文件讀取一幀壓縮數(shù)據(jù)。avcodec_decode_video2()
:解碼一幀壓縮數(shù)據(jù)。
3.FFmpeg接口使用
在使用FFmpeg解碼媒體文件之前,首先需要注冊了容器和編解碼器有關(guān)的組件。
av_register_all()
如果我們需要播放網(wǎng)絡(luò)多媒體,則可以加載socket庫以及網(wǎng)絡(luò)加密協(xié)議相關(guān)的庫,為后續(xù)使用網(wǎng)絡(luò)相關(guān)提供支持。
avformat_network_init();
我們通過avformat_open_input()
來打開一個(gè)媒體文件,并獲得媒體文件封裝格式的上下文
//打開一個(gè)文件并解析??山馕龅膬?nèi)容包括:視頻流、音頻流、視頻流參數(shù)、音頻流參數(shù)、視頻幀索引 int res = avformat_open_input(&pAVFormatCtx, url, NULL, NULL); LOGI("avformat_open_input %s %d", url, res); if(res != 0){ LOGE("can not open url :%s", url); callJava->onCallError(CHILD_THREAD, 1001, "can not open url"); exit = true; pthread_mutex_unlock(&init_mutex); return; }
通過avformat_find_stream_info()
獲取媒體文件中,提取流的上下文信息,分離出音視頻流。
//解碼時(shí),作用是從文件中提取流信,將所有的Stream的MetaData信息填充好,先read_packet一段數(shù)據(jù)解碼分析流數(shù)據(jù) if(avformat_find_stream_info(pAVFormatCtx, NULL) < 0){ LOGE("can not find streams from %s", url); callJava->onCallError(CHILD_THREAD, 1002,"can not find streams from url"); exit = true; pthread_mutex_unlock(&init_mutex); return; }
通過遍歷找出文件中的音頻流或視頻流
for(int i = 0; i < pAVFormatCtx->nb_streams; i++){ if(pAVFormatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) { //得到音頻流 if(audio == NULL){ audio = new FFAudio(playstatus, pAVFormatCtx->streams[i]->codecpar->sample_rate, callJava); audio->streamIndex = i; audio->codecpar = pAVFormatCtx->streams[i]->codecpar; audio->duration = pAVFormatCtx->duration / AV_TIME_BASE; audio->time_base = pAVFormatCtx->streams[i]->time_base; duration = audio->duration; //av_q2d(time_base)=每個(gè)刻度是多少秒 LOGI("audio stream_info[%d], duration:%d, time_base den:%d,sample_rate:%d",i, audio->duration, audio->time_base.den, pAVFormatCtx->streams[i]->codecpar->sample_rate); LOGI("audio stream_info[%d], duration %lld", i, pAVFormatCtx->duration); } } else if (pAVFormatCtx->streams[i]->codecpar->codec_type ==AVMEDIA_TYPE_VIDEO){ //得到視頻流 if (video == NULL){ video = new FFVideo(playstatus, callJava); video->streamIndex = i; video->codecpar = pAVFormatCtx->streams[i]->codecpar; video->time_base = pAVFormatCtx>streams[i]->time_base; int num = pAVFormatCtx->streams[i]->avg_frame_rate.num; int den = pAVFormatCtx->streams[i]->avg_frame_rate.den; LOGI("video stream_info[%d], frame_rate num %d,den %d", i, num,den); if(num != 0 && den != 0){ int fps = num / den;//[25 / 1] video->defaultDelayTime = 1.0 / fps; } LOGI("video stream_info[%d], defaultDelayTime is %f", i, video->defaultDelayTime); } } }
分離出音視頻流之后,可以找到對應(yīng)AVCodecContext,即編解碼器的上下文,用來尋找對應(yīng)的解碼器并設(shè)置。
//查找對應(yīng)的解碼器 存儲(chǔ)編解碼器信息的結(jié)構(gòu)體 AVCodec *avCodec = avcodec_find_decoder(codecpar->codec_id);// 軟解 //avCodec = avcodec_find_decoder_by_name("mp3_mediacodec"); // 硬解 if (!avCodec){ LOGE("MFFmpeg::getCodecContext can not find decoder!"); callJava->onCallError(CHILD_THREAD, 1003, "can not find decoder"); exit = true; pthread_mutex_unlock(&init_mutex); return -1; } LOGI("getCodecContext codecpar-> 解碼類型:%d 編碼格式:%s" , codecpar->codec_type, avCodec->name); //配置解碼器 *avCodecContext = avcodec_alloc_context3(avCodec); if (!*avCodecContext){ LOGE("can not alloc new decodecctx"); callJava->onCallError(CHILD_THREAD, 1004, "can not alloc new decodecctx"); exit = true; pthread_mutex_unlock(&init_mutex); return -1; }
通過avcodec_open2()
打開解碼器,解碼媒體文件。
//打開編解碼器 if(avcodec_open2(*avCodecContext, avCodec, 0) != 0){ LOGE("cant not open strames"); callJava->onCallError(CHILD_THREAD, 1006, "cant not open strames"); exit = true; pthread_mutex_unlock(&init_mutex); return -1; }
所以第2,3,4,5四個(gè)步驟使用的關(guān)系如下圖
打開解碼器之后,通過av_read_frame()
一幀一幀讀取壓縮數(shù)據(jù)。
AVPacket \*avPacket = av\_packet\_alloc(); //讀取具體的音/視頻幀數(shù)據(jù) int ret = av_read_frame(pAVFormatCtx, avPacket); if (ret==0){ //stream_index:標(biāo)識(shí)該AVPacket所屬的視頻/音頻流 if(avPacket->stream_index == audio->streamIndex){ //LOGI("audio 解碼第 %d 幀 DTS:%lld PTS:%lld", count, avPacket->dts,avPacket->pts); audio->queue->putAVpacket(avPacket); } else if(avPacket->stream_index == video->streamIndex){ //LOGI("video 解碼第 %d 幀 DTS:%lld PTS:%lld", count, avPacket->dts,avPacket->pts); count++; video->queue->putAVpacket(avPacket); } else{ av_packet_free(&avPacket); av_free(avPacket); avPacket = NULL; } }
通過avcodec_decode_video2()/avcodec_decode_audio4
解碼一幀視頻或者音壓縮數(shù)據(jù),通過AVPacket->AVFrame得到視頻像素?cái)?shù)據(jù)。
//解碼AVPacket->AVFrame ret = avcodec_decode_audio4(pCodeCtx, frame, &got_frame, packet); //解碼一幀視頻壓縮數(shù)據(jù),得到視頻像素?cái)?shù)據(jù) ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, packet);
三丶Clang編譯FFmpeg常見問題
1、命令找不到
錯(cuò)誤信息:
./build_android.sh: line 18: --enable-shared: command not found
./build_android.sh: line 20: --disable-static: command not found
./build_android.sh: line 22: --disable-doc: command not found
./build_android.sh: line 24: --disable-ffmpeg: command not found
./build_android.sh: line 26: --disable-ffplay: command not found
./build_android.sh: line 28: --disable-ffprobe: command not found
./build_android.sh: line 30: --disable-ffserver: command not found
./build_android.sh: line 32: --disable-avdevice: command not found
解決: 如果是直接copy網(wǎng)上的shell腳本,可能會(huì)是dos格式,請使用dos2unix build_android.sh
轉(zhuǎn)換一下,刪掉多余空格(這一點(diǎn)非常重要dos2unix 是一個(gè)工具,如果沒有安裝的話請先安裝一下:brew install dos2unix
,很快就完事。
2.xmakefile 文件沒有生成
錯(cuò)誤信息:
./android_config.sh: line 36: --enable-shared: command not found
Makefile:2: ffbuild/config.mak: No such file or directory
Makefile:40: /tools/Makefile: No such file or directory
Makefile:41: /ffbuild/common.mak: No such file or directory
Makefile:91: /libavutil/Makefile: No such file or directory
Makefile:91: /ffbuild/library.mak: No such file or directory
Makefile:93: /fftools/Makefile: No such file or directory
Makefile:94: /doc/Makefile: No such file or directory
Makefile:95: /doc/examples/Makefile: No such file or directory
Makefile:160: /tests/Makefile: No such file or directory
make: *** No rule to make target `/tests/Makefile'. Stop.
Makefile:2: ffbuild/config.mak: No such file or directory
Makefile:40: /tools/Makefile: No such file or directory
Makefile:41: /ffbuild/common.mak: No such file or directory
Makefile:91: /libavutil/Makefile: No such file or directory
Makefile:91: /ffbuild/library.mak: No such file or directory
Makefile:93: /fftools/Makefile: No such file or directory
Makefile:94: /doc/Makefile: No such file or directory
Makefile:95: /doc/examples/Makefile: No such file or directory
Makefile:160: /tests/Makefile: No such file or directory
解決: 執(zhí)行 ./configure --disable-x86asm
生成config.mak
文件
3.arm-linxu-androideabi-gcc is unable to
create an executable file
錯(cuò)誤信息:
/Users/aria/dev/android/sdk/ndk-bundle/toolchains/arm-linux-androideabi-
4.9/prebuilt/darwin-x86_64/bin/arm-linux-androideabi-gcc is unable to create an
executable file.
原因: 檢查ndk版本,android官方從 r18b 開始,已經(jīng)移除了gcc這個(gè)編譯工具詳情見ndk r18b修訂內(nèi)容 解決: 使用clang進(jìn)行編譯
4./android_config.sh: line 32: xxxxx No such file or directory
原因: .configure
后面的命令不能有注釋
解決: 刪除注釋的哪一行代碼
5、static declaration of 'xxx' follows non-static declaration
解決: config.h 搜索 lrint、lrintf、round、roundf 等對于的字符,將0修改為1
#define HAVE_LLRINT 1 #define HAVE_LLRINTF 1 #define HAVE_LRINT 1 #define HAVE_LRINTF 1 #define HAVE_ROUND 1 #define HAVE_ROUNDF 1 #define HAVE_CBRT 1 #define HAVE_CBRTF 1 #define HAVE_COPYSIGN 1 #define HAVE_TRUNC 1 #define HAVE_TRUNCF 1 #define HAVE_RINT 1 #define HAVE_HYPOT 1 #define HAVE_ERF 1
或直接使用 sed 來修改 config.h 文件
sed -i -e 's/#define HAVE_LLRINT 0/#define HAVE_LLRINT 1/g' config.h sed -i -e 's/#define HAVE_LLRINTF 0/#define HAVE_LLRINTF 1/g' config.h sed -i -e 's/#define HAVE_LRINT 0/#define HAVE_LRINT 1/g' config.h sed -i -e 's/#define HAVE_LRINTF 0/#define HAVE_LRINTF 1/g' config.h sed -i -e 's/#define HAVE_ROUND 0/#define HAVE_ROUND 1/g' config.h sed -i -e 's/#define HAVE_ROUNDF 0/#define HAVE_ROUNDF 1/g' config.h sed -i -e 's/#define HAVE_CBRT 0/#define HAVE_CBRT 1/g' config.h sed -i -e 's/#define HAVE_CBRTF 0/#define HAVE_CBRTF 1/g' config.h sed -i -e 's/#define HAVE_COPYSIGN 0/#define HAVE_COPYSIGN 1/g' config.h sed -i -e 's/#define HAVE_TRUNC 0/#define HAVE_TRUNC 1/g' config.h sed -i -e 's/#define HAVE_TRUNCF 0/#define HAVE_TRUNCF 1/g' config.h sed -i -e 's/#define HAVE_RINT 0/#define HAVE_RINT 1/g' config.h sed -i -e 's/#define HAVE_HYPOT 0/#define HAVE_HYPOT 1/g' config.h sed -i -e 's/#define HAVE_ERF 0/#define HAVE_ERF 1/g' config.h sed -i -e 's/#define HAVE_GMTIME_R 0/#define HAVE_GMTIME_R 1/g' config.h sed -i -e 's/#define HAVE_LOCALTIME_R 0/#define HAVE_LOCALTIME_R 1/g' config.h sed -i -e 's/#define HAVE_INET_ATON 0/#define HAVE_INET_ATON 1/g' config.h
6、xxxxxxxxxx error: expected ')'
錯(cuò)誤信息:
#define getenv(x) NULL
^
/home/cd008/diska/android-ndk-r9/platforms/android-18/archarm/usr/include/stdlib.h:54:14: note: in expansion of macro 'getenv'
extern char *getenv(const char *);
^
./config.h:17:19: error: expected ')' before numeric constant
#define getenv(x) NULL
^
/home/cd008/diska/android-ndk-r9/platforms/android-18/archarm/usr/include/stdlib.h:54:14: note: in expansion of macro 'getenv'
extern char *getenv(const char *);
解決: 在config.h中注釋掉#define getenv(x) NULL /#define getenv(x) NULL/
sed -i -e 's/#define getenv(x) NULL/\/\*#define getenv(x) NULL\*\//g' config.h
7、arm-linux-androideabi-ld -Wl,- soname,libavutil.so unknown option
錯(cuò)誤信息:
Users/aria/dev/android/sdk/ndk-bundle/toolchains/arm-linux-androideabi-
4.9/prebuilt/darwin-x86_64/bin/arm-linux-androideabi-ld -Wl,-soname
原因: gcc 構(gòu)建 .so 的命令是 -shared -wl,soname,xxxx.so 而 clang 的是 -shared -soname xxx.so
解決: 修改 ffbuild/config.mak
文件,將 SHFLAGS=-shared -Wl,-soname,$(SLIBNAME)
修改為 SHFLAGS=- shared -soname $(SLIBNAME)
以上就是Android FFmpeg音視頻解碼播放示例詳解的詳細(xì)內(nèi)容,更多關(guān)于Android FFmpeg音視頻解碼播放的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Android打賞功能實(shí)現(xiàn)代碼(支付寶轉(zhuǎn)賬)
這篇文章主要介紹了Android打賞功能之支付寶轉(zhuǎn)賬 ,需要的朋友可以參考下2017-12-12Android中MPAndroidChart自定義繪制最高點(diǎn)標(biāo)識(shí)的方法
目前在做一款軟件,要求在展示走勢圖的時(shí)候?qū)ψ罡唿c(diǎn)進(jìn)行自定義繪制,下面這篇文章主要給大家介紹了關(guān)于Android中MPAndroidChart自定義繪制最高點(diǎn)標(biāo)識(shí)的方法,文中通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考借鑒,下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。2018-03-03Android中WebView與Js交互的實(shí)現(xiàn)方法
本文給大家介紹android中webview與js交互的實(shí)現(xiàn)方法,本文介紹的非常詳細(xì),具有參考借鑒價(jià)值,感興趣的朋友一起學(xué)習(xí)2016-05-05提升Android應(yīng)用視覺吸引效果的10個(gè)UI設(shè)計(jì)技巧
在Android應(yīng)用開發(fā)中,風(fēng)格和設(shè)計(jì)或許不是最關(guān)鍵的要素,但它們在決定Android應(yīng)用成功與否上確實(shí)扮演重要的角色,以下是10個(gè)Android應(yīng)用的UI設(shè)計(jì)技巧,還有個(gè)附加技巧,感興趣的朋友可以了解下哦2013-01-01Android 使用幀動(dòng)畫內(nèi)存溢出解決方案
這篇文章主要介紹了Android 使用幀動(dòng)畫內(nèi)存溢出解決方案的相關(guān)資料,這里提供了詳細(xì)的解決辦法,具有參考價(jià)值,需要的朋友可以參考下2016-12-12SDL2和OpenGL使用踩坑筆記經(jīng)驗(yàn)分享
今天小編就為大家分享一篇關(guān)于SDL2和OpenGL使用踩坑筆記經(jīng)驗(yàn)分享,小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來看看吧2018-12-12解決webview內(nèi)的iframe中的事件不可用的問題
這篇文章主要介紹了解決webview內(nèi)的iframe中的事件不可用的問題,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-03-03Android實(shí)戰(zhàn)打飛機(jī)游戲之菜單頁面設(shè)計(jì)(1)
這篇文章主要為大家詳細(xì)介紹了Android實(shí)戰(zhàn)打飛機(jī)游戲之菜單頁面設(shè)計(jì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-07-07