You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

将FFmpeg播放器移植到Android 2.1的问题求助

早期Android版本FFmpeg移植与播放问题

我查阅了大量早期Android版本构建与使用FFmpeg的资料,研究2011-2014年的播放器源码,已在NDKv5平台成功构建FFmpeg 4.0.4和3.1.4版本。核心要点如下:

  • Android 2.2(API Level 8)之前不存在<android/bitmap.h>和<android/native_window.h>头文件
  • 音视频流需额外实现缓冲区管理,否则播放时会因溢出数秒后崩溃(附C++和Java代码示例)
  • FFmpeg是支持Android 2.1及以上未官方收录编解码器的唯一方案

C++解码核心代码

void decodeVideoFromPacket(JNIEnv *env, jobject instance,
                           jclass mplayer_class, AVPacket avpkt, 
                           int total_frames, int length) {
    AVFrame     *pFrame = NULL
    AVFrame     *pFrameRGB = NULL;
    pFrame = avcodec_alloc_frame();
    pFrameRGB = avcodec_alloc_frame();
    int frame_size = avpicture_get_size(PIX_FMT_RGB32, gVideoCodecCtx->width, gVideoCodecCtx->height);
    unsigned char* buffer = (unsigned char*)av_malloc((size_t)frame_size * 3);
    if (!buffer) {
        av_free(pFrame);
        av_free(pFrameRGB);
        return;
    }
    jbyteArray buffer2;
    jmethodID renderVideoFrames = env->GetMethodID(mplayer_class, "renderVideoFrames", "([BI)V");
    int frameDecoded;
    avpicture_fill((AVPicture*) pFrame,
                   buffer,
                   gVideoCodecCtx->pix_fmt,
                   gVideoCodecCtx->width,
                   gVideoCodecCtx->height
                  );

    if (avpkt.stream_index == gVideoStreamIndex) { // If video stream found
        int size = avpkt.size;
        total_frames++;
        struct SwsContext *img_convert_ctx = NULL;
        avcodec_decode_video2(gVideoCodecCtx, pFrame, &frameDecoded, &avpkt);
        if (!frameDecoded || pFrame == NULL) {
            return;
        }

        try {
            PixelFormat pxf;
            // RGB565 by default for Android Canvas in pre-Gingerbread devices.
            if(android::get_android_api_version(env) >= ANDROID_API_CODENAME_GINGERBREAD) {
                pxf = PIX_FMT_BGR32;
            } else {
                pxf = PIX_FMT_RGB565;
            }

            int rgbBytes = avpicture_get_size(pxf, gVideoCodecCtx->width,
                                            gVideoCodecCtx->height);

            // Converting YUV to RGB frame & RGB frame to char* buffer 
            
            buffer = convertYuv2Rgb(pxf, pFrame, rgbBytes); // result of av_image_copy_to_buffer()

            if(buffer == NULL) {
                return;
            }

            buffer2 = env->NewByteArray((jsize) rgbBytes);
            env->SetByteArrayRegion(buffer2, 0, (jsize) rgbBytes,
                                    (jbyte *) buffer);
            env->CallVoidMethod(instance, renderVideoFrames, buffer2, rgbBytes);
            env->DeleteLocalRef(buffer2);
            free(buffer);
        } catch (...) {
            if (debug_mode) {
                LOGE(10, "[ERROR] Render video frames failed");
                return;
            }
        }
    }
}

Java渲染核心代码

private void renderVideoFrames(final byte[] buffer, final int length) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                Canvas c;
                VideoTrack track = null;
                for (int tracks_index = 0; tracks_index < tracks.size(); tracks_index++) {
                    if (tracks.get(tracks_index) instanceof VideoTrack) {
                        track = (VideoTrack) tracks.get(tracks_index);
                    }
                }
                if (track != null) {
                    int frame_width = track.frame_size[0];
                    int frame_height = track.frame_size[1];
                    if (frame_width > 0 && frame_height > 0) {
                        try {
                            // RGB_565  == 65K colours (16 bit)
                            // RGB_8888 == 16.7M colours (24 bit w/ alpha ch.)
                            int bpp = Build.VERSION.SDK_INT > 9 ? 16 : 24;
                            Bitmap.Config bmp_config =
                                    bpp == 24 ? Bitmap.Config.RGB_565 : Bitmap.Config.ARGB_8888;
                            Paint paint = new Paint();
                            if(buffer != null && holder != null) {
                                holder.setType(SurfaceHolder.SURFACE_TYPE_NORMAL);
                                if((c = holder.lockCanvas()) == null) {
                                    Log.d(MPLAY_TAG, "Lock canvas failed");
                                    return;
                                }
                                ByteBuffer bbuf =
                                        ByteBuffer.allocateDirect(minVideoBufferSize);
                                bbuf.rewind();
                                for(int i = 0; i < buffer.length; i++) {
                                    bbuf.put(i, buffer[i]);
                                }
                                bbuf.rewind();

                                // The approximate location where the application crashed.
                                Bitmap bmp = Bitmap.createBitmap(frame_width, frame_height, bmp_config);
                                bmp.copyPixelsFromBuffer(bbuf);
                                
                                float aspect_ratio = (float) frame_width / (float) frame_height;
                                int scaled_width = (int)(aspect_ratio * (c.getHeight()));
                                c.drawBitmap(bmp,
                                        null,
                                        new RectF(
                                                ((c.getWidth() - scaled_width) / 2), 0,
                                                ((c.getWidth() - scaled_width) / 2) + scaled_width,
                                                c.getHeight()),
                                        null);
                                holder.unlockCanvasAndPost(c);
                                bmp.recycle();
                                bbuf.clear();
                            } else {
                                Log.d(MPLAY_TAG, "Video frame buffer is null");
                            }
                        } catch (Exception ex) {
                            ex.printStackTrace();
                        } catch (OutOfMemoryError oom) {
                            oom.printStackTrace();
                            stop();
                        }
                    }
                }
            }
        }).start();
    }

测试异常信息

E/dalvikvm-heap: Out of memory on a 1228812-byte allocation
I/dalvikvm: "Thread-495" prio=5 tid=21 RUNNABLE
   ................................................
     at android.graphics.Bitmap.nativeCreate(Native Method)
     at android.graphics.Bitmap.createBitmap(Bitmap.java:640)
     at android.graphics.Bitmap.createBitmap(Bitmap.java:620)
     at [app_package_name].MediaPlayer$5.run(MediaPlayer.java:406)
     at java.lang.Thread.run(Thread.java:856)

补充说明:我最初通过自研构建脚本在Ubuntu 12.04 LTS虚拟机编译FFmpeg 0.11.x,寻找Android 2.2以下的播放器示例(资料极少),但播放器打开文件显示几帧后因栈或缓冲区溢出崩溃,遂暂时搁置开发。

请问是否有现成的单C++文件方案,可兼顾向早期Android版本移植的所有细节?感谢解答。


内容的提问来源于stack exchange,提问作者tretdm

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 00:31:29