文档章节

android ndk 快速渲染yuv数据

Mizuki1412
 Mizuki1412
发布于 2015/03/15 10:10
字数 1540
阅读 570
收藏 0

因项目需要,要实现pc/windows传屏到android端显示,项目已经采用ffmpeg编码解码,现在碰到了如何将解码得到的yuv数据渲染到android surfaceview 上的问题。如果将yuv转换成rgb,软件实现的方法倒是简单,但是项目要求实时性,这种效率肯定是极低的。

下面是我整理的一些方法:

这篇文章给出了大致的方案参考 

这篇举例yuv转rgb的一些方式

至于通过yuv转rgb,然后渲染rgb的这种方式,这里就不再赘述了。

1、ANative Window API

针对android2.3以后的版本。
好像只能渲染RGB ?没有进行过测试。
header:frameworks/native/include/android/native_window.h
——参考自《Pro Android C++ with the NDK》

2、 OpenGL ES 2.0:

当平台有GPU的情况下,并且android2.2以上。
“虽然也是将 YUV 数据转 RGB 数据再显示,但是是通过GPU 来处理的,不同于利用 CPU 来进行颜色空间转换,速度快了很多”。glViewport(..) 是设置显示窗口的大小和位置,bindTexture(..)是分别绑定Y,U,V 数据。

这篇文章是用java实现的gl播放yuv

这篇文章是用c实现的,还未测试效果。

opengl的方式我也写过,但由于stagefright api的方法,并没有测试下去,以后有机会再测。

3、Private C++ API

android团队不推荐使用。The performance in this method is also the same as the ANativeWindow one(难道也只能渲染rgb?)
eg:lash, Firefox, VPlayer, MX Player and VLC for Android

这种没有深入研究,还不知道具体里面是采用了什么。

4、stagefright api

我不知道是不是应该归类在private c++ api下,不过这个的确更加平台相关,而且不是开放接口。
这里渲染有两个类:
AwesomeLocalRenderer-SoftwareRenderer
AwsomeNativeWindowRenderer- 硬件方式。

这篇文章有对此方法较为详细的描述 ,我也是主要参考它的。

header:system/core/include/system/window.h

SoftwareRenderer中有ColorConverter,也有直接render yuv的部分,而我们用的就是直接render。

ffmpeg解码出来是PIX_FMT_YUV420P,查资料应该是和OMX_COLOR_FormatYUV420Planar一样的,那么应该能直接应用softwarerenderer中的if (mColorFormat == OMX_COLOR_FormatYUV420Planar) 这部分代码,而buf的颜色空间用的是HAL_PIXEL_FORMAT_YV12 。(同属于 YUV420planar格式)

下面直接给代码吧:(以下代码只是render和ffmpeg解码部分,仅供参考)

// edu_zjgsu_mrtspserver_server_X264.cpp
extern "C"
{
#ifndef __STDC_CONSTANT_MACROS
#  define __STDC_CONSTANT_MACROS
#endif
#include <math.h>
#include <libavutil/rational.h>
#include <libavutil/opt.h>
#include <libavcodec/avcodec.h>
#include <libavutil/channel_layout.h>
#include <libavutil/imgutils.h>
#include <libavutil/mathematics.h>
#include <libavutil/samplefmt.h>
#include <libswscale/swscale.h>
};
#include <jni.h>
#include <android_runtime/AndroidRuntime.h>
#include <android_runtime/android_view_Surface.h>
#include <gui/Surface.h>
#include <assert.h>
#include <utils/Log.h>
#include <nativehelper/JNIHelp.h>
#include <media/stagefright/foundation/ADebug.h>
#include <ui/GraphicBufferMapper.h>
#include <cutils/properties.h>
#include <android/log.h>
#include <string.h>
#include <time.h>
#include <system/window.h>
#define   LOG_TAG    "LOG_TEST"
#define   LOGI(...)  __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)
#define   LOGE(...)  __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)
using namespace android;

AVCodec *codec;
AVCodecContext *c;
AVFrame *m_pYUVFrame; //YUV帧数据
uint8_t *yuv_buffer;
sp<Surface> surface;

int ALIGN(int x, int y) {
    // y must be a power of 2.
    return (x + y - 1) & ~(y - 1);
}

/** 渲染 **/
void render(const void *data, const void *data_u, const void *data_v, const sp<ANativeWindow> &nativeWindow,int width, int height) {
    sp<ANativeWindow> mNativeWindow = nativeWindow;
    int err;
    // 裁剪大小,这里暂时设置是一样的,有需要再改
    int mCropWidth = width;
    int mCropHeight = height;
    // 目前其实  mCropWidth == width == bufWidth
    int halFormat = HAL_PIXEL_FORMAT_YV12;
    int bufWidth = (mCropWidth + 1) & ~1; //按2对齐。成2的倍数(多了减1)
    int bufHeight = (mCropHeight + 1) & ~1;
    CHECK_EQ(0,
            native_window_set_usage(
            mNativeWindow.get(),
            GRALLOC_USAGE_SW_READ_NEVER | GRALLOC_USAGE_SW_WRITE_OFTEN
            | GRALLOC_USAGE_HW_TEXTURE | GRALLOC_USAGE_EXTERNAL_DISP));
    CHECK_EQ(0,
           native_window_set_scaling_mode(
           mNativeWindow.get(),
           NATIVE_WINDOW_SCALING_MODE_SCALE_CROP));
    // 分配buf显示空间
    CHECK_EQ(0, native_window_set_buffers_geometry(
                mNativeWindow.get(),
                bufWidth,
                bufHeight,
                halFormat));
    ANativeWindowBuffer *buf;
    //申请一块空闲的图形缓冲区
    if ((err = native_window_dequeue_buffer_and_wait(mNativeWindow.get(),
            &buf)) != 0) {
        LOGI("Surface::dequeueBuffer returned error %d", err);
        return;
    }
    GraphicBufferMapper &mapper = GraphicBufferMapper::get();
    Rect bounds(mCropWidth, mCropHeight);
    void *dst;
    CHECK_EQ(0, mapper.lock(//用来锁定一个图形缓冲区并将缓冲区映射到用户进程
           buf->handle, GRALLOC_USAGE_SW_WRITE_OFTEN, bounds, &dst));
// 开始适配
    const uint8_t *src_y = (const uint8_t *)data;
    const uint8_t *src_u = (const uint8_t *)data_u;
    const uint8_t *src_v = (const uint8_t *)data_v;

    uint8_t *dst_y = (uint8_t *)dst;
    size_t dst_y_size = buf->stride * buf->height;
    size_t dst_c_stride = ALIGN(buf->stride / 2, 16);
    size_t dst_c_size = dst_c_stride * buf->height / 2;
    uint8_t *dst_v = dst_y + dst_y_size;
    uint8_t *dst_u = dst_v + dst_c_size;

    for (int y = 0; y < mCropHeight; ++y) {
        memcpy(dst_y, src_y, mCropWidth);
        src_y += width;
        dst_y += buf->stride;
    }
    for (int y = 0; y < (mCropHeight + 1) / 2; ++y) {
        memcpy(dst_u, src_u, (mCropWidth + 1) / 2);
        memcpy(dst_v, src_v, (mCropWidth + 1) / 2);
        src_u += width / 2;
        src_v += width / 2;
        dst_u += dst_c_stride;
        dst_v += dst_c_stride;
    }

    CHECK_EQ(0, mapper.unlock(buf->handle));
    if ((err = mNativeWindow->queueBuffer(mNativeWindow.get(), buf, -1)) != 0) {
        LOGI("Surface::queueBuffer returned error %d", err);
    }
    buf = NULL;
}

int X264Init()
{
    int ret,got_output;
    c= NULL;

    avcodec_register_all();
    /* find the mpeg1 video encoder */
    codec = avcodec_find_decoder(AV_CODEC_ID_H264);
    if (!codec) {
        fprintf(stderr, "Codec not found\n");
        return 1;
    }
    c = avcodec_alloc_context3(codec);
    if (!c) {
        fprintf(stderr, "Could not allocate video codec context\n");
        return 2;
    }
    if(codec->capabilities&CODEC_CAP_TRUNCATED)
        c->flags|= CODEC_FLAG_TRUNCATED; /* we do not send complete frames */

    /* open it */
        ret = avcodec_open2(c, codec, NULL);
    if (ret < 0) {
        fprintf(stderr, "Could not open codec\n");
        return 3;
    }
    m_pYUVFrame = av_frame_alloc();
    if (!m_pYUVFrame) {
        fprintf(stderr, "Could not allocate video frame\n");
        return 6;
    }
    yuv_buffer = NULL;
    return 0;
}

/** 解码,传入一帧数据和大小 **/
int DecodeFrame(uint8_t *pInBuf, int iInBufSize)
{
    AVPacket pkt;
    int ret,got_output=0;
    av_init_packet(&pkt);
    pkt.data = pInBuf;    // packet data will be allocated by the encoder
    pkt.size = iInBufSize;
    /* encode the image */
    ret = avcodec_decode_video2(c, m_pYUVFrame,  &got_output, &pkt);
    if (ret < 0) {
        fprintf(stderr, "Error decoding frame\n");
        return 1;
    }
    LOGI("got_output=%d",got_output);
    if (got_output) {
        render(m_pYUVFrame->data[0],m_pYUVFrame->data[1],m_pYUVFrame->data[2],surface,c->width,c->height);
        av_free_packet(&pkt);
    }
    return got_output;
}

Android.mk :

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := cutils
LOCAL_SRC_FILES := prebuilt/libcutils.so
include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE := utils
LOCAL_SRC_FILES := prebuilt/libutils.so
include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE := binder
LOCAL_SRC_FILES := prebuilt/libbinder.so
include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE := ui
LOCAL_SRC_FILES := prebuilt/libui.so
include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE := gui
LOCAL_SRC_FILES := prebuilt/libgui.so
include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE := android_runtime
LOCAL_SRC_FILES := prebuilt/libandroid_runtime.so
include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE := stagefright_foundation
LOCAL_SRC_FILES := prebuilt/libstagefright_foundation.so
include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE := avformat-55
LOCAL_SRC_FILES := prebuilt/libavformat-55.so

include $(CLEAR_VARS)
LOCAL_MODULE := avutil-52
LOCAL_SRC_FILES := prebuilt/libavutil-52.so
include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE := avcodec-55
LOCAL_SRC_FILES := prebuilt/libavcodec-55.so
include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE := swscale-2
LOCAL_SRC_FILES := prebuilt/libswscale-2.so
include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE := swresample-0
LOCAL_SRC_FILES := prebuilt/libswresample-0.so
include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE    := CallFFMpeg
LOCAL_SRC_FILES := edu_zjgsu_mrtspserver_server_X264.cpp
LOCAL_LDLIBS := -llog -ljnigraphics -lz -landroid
# 一些ffmpeg的相关库和stagefright相关库
LOCAL_SHARED_LIBRARIES := \
        libavformat-55 \
        libavcodec-55 \
        libswscale-2 \
        libavutil-52 \
        libswresample-0 \
        libcuitls \
        libutils \
        libbinder \
        libui \
        libgui \
        libandroid_runtime \
        libstagefright_foundation

LOCAL_C_INCLUDES := \
    ../frameworks/native/include \
    ../frameworks/base/include\
    ../system/core/include\
    ../libnativehelper/include\
    ../hardware/libhardware/include\
    ../frameworks/av/include\
    jni/include\
    ../frameworks/native/include/media/openmax

TARGET_ARCH_ABI := armeabi-v7a
LOCAL_ARM_NEON := true
LOCAL_CXXFLAGS := -DHAVE_PTHREADS
include $(BUILD_SHARED_LIBRARY)

最后效果:1280x720图像渲染时间2-3ms。

遇到的问题:1、编译问题,因为要依赖到stagefright等其他相关的底层库,所以平台相关性较大。2、可能由于解码库的原因,如果图像width不是16的倍数,如1366,那么render时候会出现螺旋状花屏或是uv通道重影的现象。

这篇文章也提供了类似的方法,但没有详细的代码,以待研究。



© 著作权归作者所有

Mizuki1412
粉丝 1
博文 3
码字总数 1824
作品 0
杭州
程序员
私信 提问
通过FFmpeg解码和OpenGL的YUV转RGB实现Android视频播放

前言 在我的博文 https://blog.csdn.net/ericbar/article/details/80506390 中,我们在Android平台上,实现了通过FFmpeg在native(C/C++)层进行视频解码,并通过OpenGL实现了硬件渲染工作,...

ericbar
2018/05/30
0
0
【Android音视频】Android—YUV格式深入浅出

文章目录 Android音视频—YUV格式深入浅出 Android音视频—YUV格式深入浅出 文章参考: 图文详解YUV420数据格式 YUV主要采样格式理解 YUV格式详解 百度百科和维基百科 概述 本文基于Android...

sslinp
2018/11/28
0
0
编译且移植FFTW3到Android手机上(1)

本文主要对如何将FFTW3编译且移植到Android App上进行介绍,同时对各FFTW提供的一些快速傅里叶变换的方法在手机进行性能测试,总结出使用FFTW3进行小规模傅里叶变换的最佳方式。 文章重点内容...

whoisliang
2018/11/30
0
0
Android视频直播流(一)YUV的简单介绍

最近没事情做,就想着研究一下Android比较贴近硬件的部分。之前在一家音视频即时通讯公司一直对音视频感兴趣,所以就研究一下Android端的音视频通讯。 ------------------------------------...

xiaolei123
2018/03/29
0
0
Android 网络编程 目录

Android 网络编程 目录 Android 网络编程1 Http协议 Android 网络编程2 Okhttp缓存机制 Android 网络编程3 Java NIO to be continued... Android 架构师之路 目录 Android 架构师之路1 UML图...

香沙小熊
2018/06/21
0
0

没有更多内容

加载失败,请刷新页面

加载更多

利用mybatis generator生成实体类、Mapper接口以及对应的XML文件

项目中通常会遇到数据的持久化,如果是采用mybatis的orm,就会涉及到生成xml的问题,刚好mybatis官网提供了这么个插件MyBatis Generator,效果简直是棒呆。 1. 首先需要在build.gradle文件中...

啊哈关关
今天
2
0
SpringSocial相关的知识点

使用SprigSocial开发第三方登录 核心类 ServiceProvider(AbstractOauth2ServiceProvider):主要负责实现server提供商(例如QQ,微信等共有的东西),默认实现类是AbstractOauth2ServiceProvider...

chendom
今天
2
0
Java并发之AQS详解

一、概述   谈到并发,不得不谈ReentrantLock;而谈到ReentrantLock,不得不谈AbstractQueuedSynchronizer(AQS)!   类如其名,抽象的队列式的同步器,AQS定义了一套多线程访问共享资源...

群星纪元
昨天
2
0
Fabric-sdk-java最新教程

Fabric Java SDK是Fabric区块链官方提供的用于Java应用开发的SDK,全称为Fabric-sdk-java,网上可用资料不多,本文列出了精心整理的针对Fabric Java SDK的最新精选教程。 如果希望快速掌握F...

汇智网教程
昨天
3
0
react 子组件监听props 变化

componentWillReceiveProps //已经被废弃 getDerivedStateFromProps// 推荐使用//如果条件不存在必须要返回null static getDerivedStateFromProps(props, current_stat...

一箭落旄头
昨天
3
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部