Android中Gif的解码、播放

Gif的文件格式,可以参考另外两篇转载的博客:

Gif文件格式

Gif文件格式

项目中,会把Gif的解码、播放提交到一个线程池中执行,线程池的用法可以参考另一篇文章,ThreadPoolExecutor的使用

以下范例代码来源于图库Gallery,首先把Gif解码的任务提交到线程池:

mThreadPool.submit(new GifDecoderJob(item), new GifDecoderListener(item.getPath()));

其中的Item表示媒体类型,可以自定义,GifDecoderListener用于解码完成的回调,也就是解码完成会由GifDecoderListener启动Gif播放。

    private class GifDecoderJob implements Job<GifDecoder> {
        private MediaItem mItem;

        public GifDecoderJob(MediaItem item) {
            mItem = item;
        }

        @Override
        public GifDecoder run(JobContext jc) {
            if (isTemporaryItem(mItem)) {
                return null;
            }
            return new GifRequest(mItem.getContentUri(), mActivity).run(jc);
        }
    }

自定义类GifDecoderJob,处理Gif的decode。

public class GifRequest implements Job<GifDecoder> {

    private static final String TAG = "GifRequest";
    Uri itemUri;
    Context mContext;

    public GifRequest(Uri uri, Context context) {
        itemUri = uri;
        mContext = context;
    }

    private InputStream getInputStream(Uri uri) {
        ContentResolver cr = mContext.getContentResolver();
        InputStream input = null;
        try {
            input = cr.openInputStream(uri);
        } catch (IOException e) {
            Log.e(TAG, "catch exception:" + e);
        }
        return input;
    }

    @Override
    public GifDecoder run(JobContext jc) {
        InputStream input = getInputStream(itemUri);
        if (input != null) {
            return new GifDecoder(getInputStream(itemUri), null);
        } else {
            return null;
        }
    }
}

根据资源的uri,打开一个输入流,input = cr.openInputStream(uri);这个是调用frameworks/base/core/java/android/content/ContentResolver.java中的方法openInputStream();

真正执行解码的是GifDecoder 类,以下函数都来自于GifDecoder :

private InputStream mIS;
    public GifDecoder(InputStream is, GifAction act) {
        mIS = is;
        mGifAction = act;
        startDecoder();
}

startDecoder主要调用了readStream方法:

    private int readStream() {
        init();
        if (mIS != null) {
            readHeader();
            if (!err()) {
                readContents();
                if (mFrameCount < 0) {
                    mStatus = STATUS_FORMAT_ERROR;
                    if (mGifAction != null) {
                        mGifAction.parseOk(false, -1);
                    }
                } else {
                    mStatus = STATUS_FINISH;
                    if (mGifAction != null) {
                        mGifAction.parseOk(true, -1);
                    }
                }
            }
            try {
                mIS.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        } else {
            mStatus = STATUS_OPEN_ERROR;
            if (mGifAction != null) {
                mGifAction.parseOk(false, -1);
            }
        }
        return mStatus;
    }

通过readHeader读取文件头,前3个字节是Gif署名,后三个字节是版本号:

    private void readHeader() {
        String id = "";
        for (int i = 0; i < 6; i++) {
            id += (char) read();
        }
        if (!id.startsWith("GIF")) {
            mStatus = STATUS_FORMAT_ERROR;
            return;
        }
        readLSD();
        if (mGctFlag && !err()) {
            mGct = readColorTable(mGctSize);
            mBgColor = mGct[mBgIndex];
        }
    }

接着调用readLSD读取Gif的宽高、颜色方案、背景色索引、像素宽高比:

    private void readLSD() {
        // logical screen size
        mWidth = readShort();
        mHeight = readShort();
        // packed fields
        int packed = read();
        mGctFlag = (packed & 0x80) != 0; // 1 : global color table flag
        // 2-4 : color resolution
        // 5 : gct sort flag
        mGctSize = 2 << (packed & 7); // 6-8 : gct size
        mBgIndex = read(); // background color index
        mPixelAspect = read(); // pixel aspect ratio
    }

接着读取内容:

 private void readContents() {
        // read GIF file content blocks
        boolean done = false;
        while (!(done || err())) {
            int code = read();
            switch (code) {
                case 0x2C: // image separator
                    readImage();
                    break;
                case 0x21: // extension
                    code = read();
                    switch (code) {
                        case 0xf9: // graphics control extension
                            readGraphicControlExt();
                            break;
                        case 0xff: // application extension
                            readBlock();
                            String app = "";
                            for (int i = 0; i < 11; i++) {
                                app += (char) mBlock[i];
                            }
                            if (app.equals("NETSCAPE2.0")) {
                                readNetscapeExt();
                            } else {
                                skip(); // don't care
                            }
                            break;
                        default: // uninteresting extension
                            skip();
                    }
                    break;
                case 0x3b: // terminator
                    done = true;
                    break;
                case 0x00: // bad byte, but keep going and see what happens
                    break;
                default:
                    mStatus = STATUS_FORMAT_ERROR;
            }
        }
    }

通过readImage读取图像块部分,包括x、y方向偏移量,图像宽高,图像数据块:

private void readImage() {
        mIx = readShort(); // (sub)image position & size
        mIy = readShort();
        mIw = readShort();
        mIh = readShort();
        int packed = read();
        mLctFlag = (packed & 0x80) != 0; // 1 - local color table flag
        mInterlace = (packed & 0x40) != 0; // 2 - interlace flag
        // 3 - sort flag
        // 4-5 - reserved
        mLctSize = 2 << (packed & 7); // 6-8 - local color table size
        if (mLctFlag) {
            mLct = readColorTable(mLctSize); // read table
            mAct = mLct; // make local table active
        } else {
            mAct = mGct; // make global table active
            if (mBgIndex == mTransIndex) {
                mBgColor = 0;
            }
        }
        int save = 0;
        if (mTransparency) {
            save = mAct[mTransIndex];
            mAct[mTransIndex] = 0; // set transparent color if specified
        }
        if (mAct == null) {
            mStatus = STATUS_FORMAT_ERROR; // no color table defined
        }
        if (err()) {
            return;
        }
        try {
            decodeImageData(); // decode pixel data
            skip();
            if (err()) {
                return;
            }
            mFrameCount++;
            // create new image to receive frame data
            mImage = Bitmap.createBitmap(mWidth, mHeight, Config.ARGB_4444);
            // createImage(mWidth, mHeight);
            setPixels(); // transfer pixel data to image
            if (mGifFrame == null) {
                mGifFrame = new GifFrame(mImage, mDelay, mDispose);
                mCurrentFrame = mGifFrame;
            } else {
                GifFrame f = mGifFrame;
                while (f.mNextFrame != null) {
                    f = f.mNextFrame;
                }
                f.mNextFrame = new GifFrame(mImage, mDelay, mDispose);
            }
            // frames.addElement(new GifFrame(image, delay)); // add image to
            // frame
            // list
            if (mTransparency) {
                mAct[mTransIndex] = save;
            }
            resetFrame();
            if (mGifAction != null) {
                mGifAction.parseOk(true, mFrameCount);
            }
        } catch (OutOfMemoryError e) {
            Log.e("GifDecoder", ">>> log  : " + e.toString());
            e.printStackTrace();
        }
    }

由decodeImageData完成像素解码,然后将像素数据填充到Bitmap中,构建GifFrame对象。

  private void decodeImageData() {
        int NullCode = -1;
        int npix = mIw * mIh;
        int available, clear, code_mask, code_size, end_of_information, in_code, old_code,
                bits, code, count, i, datum, data_size, first, top, bi, pi;

        if ((mPixels == null) || (mPixels.length < npix)) {
            mPixels = new byte[npix]; // allocate new pixel array
        }
        if (mPrefix == null) {
            mPrefix = new short[MaxStackSize];
        }
        if (mSuffix == null) {
            mSuffix = new byte[MaxStackSize];
        }
        if (mPixelStack == null) {
            mPixelStack = new byte[MaxStackSize + 1];
        }
        // Initialize GIF data stream decoder.
        data_size = read();
        clear = 1 << data_size;
        end_of_information = clear + 1;
        available = clear + 2;
        old_code = NullCode;
        code_size = data_size + 1;
        code_mask = (1 << code_size) - 1;
        for (code = 0; code < clear; code++) {
            mPrefix[code] = 0;
            mSuffix[code] = (byte) code;
        }

        // Decode GIF pixel stream.
        datum = bits = count = first = top = pi = bi = 0;
        for (i = 0; i < npix;) {
            if (top == 0) {
                if (bits < code_size) {
                    // Load bytes until there are enough bits for a code.
                    if (count == 0) {
                        // Read a new data block.
                        count = readBlock();
                        if (count <= 0) {
                            break;
                        }
                        bi = 0;
                    }
                    datum += (((int) mBlock[bi]) & 0xff) << bits;
                    bits += 8;
                    bi++;
                    count--;
                    continue;
                }
                // Get the next code.
                code = datum & code_mask;
                datum >>= code_size;
                bits -= code_size;

                // Interpret the code
                if ((code > available) || (code == end_of_information)) {
                    break;
                }
                if (code == clear) {
                    // Reset decoder.
                    code_size = data_size + 1;
                    code_mask = (1 << code_size) - 1;
                    available = clear + 2;
                    old_code = NullCode;
                    continue;
                }
                if (old_code == NullCode) {
                    mPixelStack[top++] = mSuffix[code];
                    old_code = code;
                    first = code;
                    continue;
                }
                in_code = code;
                if (code == available) {
                    mPixelStack[top++] = (byte) first;
                    code = old_code;
                }
                while (code > clear) {
                    mPixelStack[top++] = mSuffix[code];
                    code = mPrefix[code];
                }
                first = ((int) mSuffix[code]) & 0xff;
                // Add a new string to the string table,
                if (available >= MaxStackSize) {
                    break;
                }
                mPixelStack[top++] = (byte) first;
                mPrefix[available] = (short) old_code;
                mSuffix[available] = (byte) first;
                available++;
                if (((available & code_mask) == 0)
                        && (available < MaxStackSize)) {
                    code_size++;
                    code_mask += available;
                }
                old_code = in_code;
            }

            // Pop a pixel off the pixel stack.
            top--;
            mPixels[pi++] = mPixelStack[top];
            i++;
        }
        for (i = pi; i < npix; i++) {
            mPixels[i] = 0; // clear missing pixels
        }
    }

readImage之后,读取的是图形扩展部分,更多源码详见附件文件。

解码完成后,怎么播放Gif?

  private class GifDecoderListener
            implements Runnable, FutureListener<GifDecoder> {
        private final Path mPath;
        private Future<GifDecoder> mFuture;

        public GifDecoderListener(Path path) {
            mPath = path;
        }

        @Override
        public void onFutureDone(Future<GifDecoder> future) {
            mFuture = future;
            if (null != mFuture.get()) {
                mMainHandler.sendMessage(
                        mMainHandler.obtainMessage(MSG_RUN_OBJECT, this));
            }
        }

        @Override
        public void run() {
            updateGifDecoder(mPath, mFuture);
        }
    }

在GifDecoder执行完成后,会调用Listener的OnFeatureDone,进一步,Gif的播放是在一个Runnable中完成的:

    private class GifRunnable implements Runnable {

        private GifEntry mGifEntry;
        private Path mPath;

        private void free() {
            if (null != mGifEntry) {
                if (null != mGifEntry.gifDecoder) {
                    mGifEntry.gifDecoder.free();
                    mGifEntry.gifDecoder = null;
                }
                mGifEntry = null;
            }
        }

        public GifRunnable(Path path, GifEntry gifEntry) {
            mPath = path;
            mGifEntry = gifEntry;
            if (null == mGifEntry || null == mGifEntry.gifDecoder) {
                free();
                return;
            }
            boolean imageChanged = mGifEntry.animatedIndex != mCurrentIndex;
            MediaItem item = getMediaItem(0);
            Path currentPath = (item != null ? item.getPath() : null);
            imageChanged |= path != currentPath;
            if (imageChanged) {
                free();
                return;
            }
            mGifEntry.currentFrame = 0;
            mGifEntry.totalFrameCount = mGifEntry.gifDecoder.getFrameCount();
            if (mGifEntry.totalFrameCount <= 1) {
                free();
                return;
            }
        }

        @Override
        public void run() {
            if (!mIsActive) {
                free();
                return;
            }

            if (null == mGifEntry || null == mGifEntry.gifDecoder) {
                free();
                return;
            }

            boolean imageChanged = mGifEntry.animatedIndex != mCurrentIndex;
            MediaItem item = getMediaItem(0);
            Path currentPath = (item != null ? item.getPath() : null);
            imageChanged |= mPath != currentPath;
            if (imageChanged) {
                free();
                return;
            }

            Bitmap frameBitmap = mGifEntry.gifDecoder.getFrameImage(mGifEntry.currentFrame);
            if (null == frameBitmap) {
                free();
                return;
            }
            long delay = (long) mGifEntry.gifDecoder.getDelay(mGifEntry.currentFrame);
            mGifEntry.currentFrame = (mGifEntry.currentFrame + 1) % mGifEntry.totalFrameCount;

            ScreenNail gifFrame = new BitmapScreenNail(frameBitmap);
            if (mGifEntry.entry.currentGifFrame != null) {
                mGifEntry.entry.currentGifFrame.recycle();
                mGifEntry.entry.currentGifFrame = null;
            }
            mGifEntry.entry.currentGifFrame = gifFrame;
            updateTileProvider(mGifEntry.entry);
            mPhotoView.notifyImageChange(0);
            mMainHandler.sendMessageDelayed(
                    mMainHandler.obtainMessage(MSG_RUN_OBJECT, this), delay);
        }
    }

会根据时间间隔、GifFrame帧数,循环投递播放消息,即:            

mMainHandler.sendMessageDelayed( mMainHandler.obtainMessage(MSG_RUN_OBJECT, this), delay);

直到收到退出事件,调用free释放资源。

GifDecoder.java的源码可以下载:http://download.csdn.net/download/lin20044140410/10251700



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值