本篇實(shí)現(xiàn)一個(gè)有意思的玩意兒,視頻壁紙,相機(jī)壁紙
先上圖哈哈:
1.視頻壁紙 2.相機(jī)壁紙


先從動(dòng)態(tài)壁紙的實(shí)現(xiàn)說(shuō)起吧:
每一個(gè)動(dòng)態(tài)壁紙都繼承自WallpaperService,其中必須實(shí)現(xiàn)的抽象方法onCreateEngine,返回一個(gè)Engine對(duì)象,實(shí)際上所有的繪圖與刷新都是由engine完成。如下:
public class VideoLiveWallpaper extends WallpaperService {
// 實(shí)現(xiàn)WallpaperService必須實(shí)現(xiàn)的抽象方法
public Engine onCreateEngine() {
return new VideoEngine();
}
class VideoEngine extends Engine {
@Override
public void onCreate(SurfaceHolder surfaceHolder) {
super.onCreate(surfaceHolder);
// 設(shè)置處理觸摸事件
setTouchEventsEnabled(true);
}
}
}
必須在清單文件中進(jìn)行一些配置,比如:
<!-- 配置動(dòng)態(tài)壁紙Service -->
<service android:label="@string/app_name"
android:name=".LiveWallpaper"
android:permission="android.permission.BIND_WALLPAPER">
<!-- 為動(dòng)態(tài)壁紙配置intent-filter -->
<intent-filter>
<action android:name="android.service.wallpaper.WallpaperService" />
</intent-filter>
<!-- 為動(dòng)態(tài)壁紙配置meta-data -->
<meta-data android:name="android.service.wallpaper"
android:resource="@xml/livewallpaper" />
</service>
比較重要的部分首先是權(quán)限android:permission=”android.permission.BIND_WALLPAPER”;
其次service需要響應(yīng)action:android:name=”android.service.wallpaper.WallpaperService;
接下來(lái)接收配置文件。首先在res文件夾下建立一個(gè)xml目錄,和寫(xiě)appwidget一樣。在目錄下我們創(chuàng)建一個(gè)xml文件:
<wallpaper xmlns:android="http://schemas.android.com/apk/res/android"
android:settingsActivity="LiveWallPreference"
android:thumbnail="@drawable/ic_launcher"
android:description="@string/wallpaper_description"
/>
然后啟動(dòng)選擇壁紙的代碼是這樣的:
final Intent pickWallpaper = new Intent(Intent.ACTION_SET_WALLPAPER);
Intent chooser = Intent.createChooser(pickWallpaper, getString(R.string.choose_wallpaper));
startActivity(chooser);
實(shí)現(xiàn)視頻壁紙
實(shí)現(xiàn)視頻壁紙的時(shí)候本來(lái)打算用mediaplayer實(shí)現(xiàn),后來(lái)發(fā)現(xiàn)mediaplayer實(shí)現(xiàn)在某些機(jī)型上報(bào)JNI層錯(cuò)誤。
于是改用ffmpeg自己實(shí)現(xiàn)JNI層,當(dāng)然這樣做的好處是可以更多的定制化,比如示例上的快速播放視頻。
主要就一個(gè)函數(shù)
即把WallpaperService 的Surface傳給native的play方法。
public class VideoLiveWallpaper extends WallpaperService {
// 實(shí)現(xiàn)WallpaperService必須實(shí)現(xiàn)的抽象方法
public Engine onCreateEngine() {
return new VideoEngine();
}
class VideoEngine extends Engine {
@Override
public void onCreate(SurfaceHolder surfaceHolder) {
super.onCreate(surfaceHolder);
play(getSurfaceHolder().getSurface());
// 設(shè)置處理觸摸事件
setTouchEventsEnabled(true);
}
static {
System.loadLibrary("native-lib");
}
public native int play(Object surface);
}
```
然后JNI的play方法具體實(shí)現(xiàn)。
關(guān)鍵地方都有注釋?zhuān)梢越Y(jié)合我之前分享的ffmpeg源碼看
ffmpeg源碼簡(jiǎn)析(一)結(jié)構(gòu)總覽 :[http://blog.csdn.net/king1425/article/details/70597642
下面是JNI代碼:
```C++
JNIEXPORT jint JNICALL
Java_com_ws_ffmpegandroidwallpaper_VideoLiveWallpaper_play
(JNIEnv *env, jclass clazz, jobject surface) {
LOGD("play");
// sd卡中的視頻文件地址,可自行修改或者通過(guò)jni傳入
//char *file_name = "/storage/emulated/0/ws.mp4";
char *file_name = "/storage/emulated/0/video.avi";
av_register_all();
AVFormatContext *pFormatCtx = avformat_alloc_context();
// Open video file
if (avformat_open_input(&pFormatCtx, file_name, NULL, NULL) != 0) {
LOGD("Couldn't open file:%s\n", file_name);
return -1; // Couldn't open file
}
// Retrieve stream information
if (avformat_find_stream_info(pFormatCtx, NULL) < 0) {
LOGD("Couldn't find stream information.");
return -1;
}
// Find the first video stream
int videoStream = -1, i;
for (i = 0; i < pFormatCtx->nb_streams; i++) {
if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO
&& videoStream < 0) {
videoStream = i;
}
}
if (videoStream == -1) {
LOGD("Didn't find a video stream.");
return -1; // Didn't find a video stream
}
// Get a pointer to the codec context for the video stream
AVCodecContext *pCodecCtx = pFormatCtx->streams[videoStream]->codec;
// Find the decoder for the video stream
AVCodec *pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
if (pCodec == NULL) {
LOGD("Codec not found.");
return -1; // Codec not found
}
if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0) {
LOGD("Could not open codec.");
return -1; // Could not open codec
}
// 獲取native window
ANativeWindow *nativeWindow = ANativeWindow_fromSurface(env, surface);
// 獲取視頻寬高
int videoWidth = pCodecCtx->width;
int videoHeight = pCodecCtx->height;
// 設(shè)置native window的buffer大小,可自動(dòng)拉伸
ANativeWindow_setBuffersGeometry(nativeWindow, videoWidth, videoHeight,
WINDOW_FORMAT_RGBA_8888);
ANativeWindow_Buffer windowBuffer;
if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0) {
LOGD("Could not open codec.");
return -1; // Could not open codec
}
// Allocate video frame
AVFrame *pFrame = av_frame_alloc();
// 用于渲染
AVFrame *pFrameRGBA = av_frame_alloc();
if (pFrameRGBA == NULL || pFrame == NULL) {
LOGD("Could not allocate video frame.");
return -1;
}
// Determine required buffer size and allocate buffer
// buffer中數(shù)據(jù)就是用于渲染的,且格式為RGBA
int numBytes = av_image_get_buffer_size(AV_PIX_FMT_RGBA, pCodecCtx->width, pCodecCtx->height,
1);
uint8_t *buffer = (uint8_t *) av_malloc(numBytes * sizeof(uint8_t));
av_image_fill_arrays(pFrameRGBA->data, pFrameRGBA->linesize, buffer, AV_PIX_FMT_RGBA,
pCodecCtx->width, pCodecCtx->height, 1);
// 由于解碼出來(lái)的幀格式不是RGBA的,在渲染之前需要進(jìn)行格式轉(zhuǎn)換
struct SwsContext *sws_ctx = sws_getContext(pCodecCtx->width,
pCodecCtx->height,
pCodecCtx->pix_fmt,
pCodecCtx->width,
pCodecCtx->height,
AV_PIX_FMT_RGBA,
SWS_BILINEAR,
NULL,
NULL,
NULL);
int frameFinished;
AVPacket packet;
while (av_read_frame(pFormatCtx, &packet) >= 0) {
// Is this a packet from the video stream?
if (packet.stream_index == videoStream) {
// Decode video frame
avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, &packet);
// 并不是decode一次就可解碼出一幀
if (frameFinished) {
// lock native window buffer
ANativeWindow_lock(nativeWindow, &windowBuffer, 0);
// 格式轉(zhuǎn)換
sws_scale(sws_ctx, (uint8_t const *const *) pFrame->data,
pFrame->linesize, 0, pCodecCtx->height,
pFrameRGBA->data, pFrameRGBA->linesize);
// 獲取stride
uint8_t *dst = (uint8_t *) windowBuffer.bits;
int dstStride = windowBuffer.stride * 4;
uint8_t *src = (pFrameRGBA->data[0]);
int srcStride = pFrameRGBA->linesize[0];
// 由于window的stride和幀的stride不同,因此需要逐行復(fù)制
int h;
for (h = 0; h < videoHeight; h++) {
memcpy(dst + h * dstStride, src + h * srcStride, srcStride);
}
ANativeWindow_unlockAndPost(nativeWindow);
}
}
av_packet_unref(&packet);
}
av_free(buffer);
av_free(pFrameRGBA);
// Free the YUV frame
av_free(pFrame);
// Close the codecs
avcodec_close(pCodecCtx);
// Close the video file
avformat_close_input(&pFormatCtx);
return 0;
}
}
```
##相機(jī)壁紙
這個(gè)簡(jiǎn)單直接上代碼了
最精華的一句: camera.setPreviewDisplay(getSurfaceHolder());
```java
package com.ws.ffmpegandroidwallpaper;
import android.hardware.Camera;
import android.service.wallpaper.WallpaperService;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import java.io.IOException;
public class CameraLiveWallpaper extends WallpaperService {
public Engine onCreateEngine() {
return new CameraEngine();
}
class CameraEngine extends Engine {
private Camera camera;
@Override
public void onCreate(SurfaceHolder surfaceHolder) {
super.onCreate(surfaceHolder);
startPreview();
// 設(shè)置處理觸摸事件
setTouchEventsEnabled(true);
}
@Override
public void onTouchEvent(MotionEvent event) {
super.onTouchEvent(event);
}
@Override
public void onDestroy() {
super.onDestroy();
stopPreview();
}
@Override
public void onVisibilityChanged(boolean visible) {
if (visible) {
startPreview();
} else {
stopPreview();
}
}
/**
* 開(kāi)始預(yù)覽
*/
public void startPreview() {
camera = Camera.open();
camera.setDisplayOrientation(90);
try {
camera.setPreviewDisplay(getSurfaceHolder());
} catch (IOException e) {
e.printStackTrace();
}
camera.startPreview();
}
/**
* 停止預(yù)覽
*/
public void stopPreview() {
if (camera != null) {
try {
camera.stopPreview();
camera.setPreviewCallback(null);
// camera.lock();
camera.release();
} catch (Exception e) {
e.printStackTrace();
}
camera = null;
}
}
}
}
```
源碼下載:http://blog.csdn.net/king1425/article/details/72236225