C++ Code Snippet -- 如何实现yuv压缩成jpeg

585 阅读3分钟

yuv格式是相机的原始格式,一般都需要压缩处理,减少体积,yuv根据不同的排列方式有不同的格式,下面列举常用的格式转换。

可以使用libjpeg库来完成压缩工作,编译好的库请移步这里下载:

download.csdn.net/download/lp…

下面是使用的demo,需要定义三个类YuvToJpegUtil.h 、YuvToJpegUtil.cpp 、main.cpp 和一个配置文件CMakeList.txt。

其中 YuvToJpegUtil.h 如下:

#include "turbojpeg.h"

typedef unsigned char       BYTE;

class YuvToJpegUtil {
public:
    bool convertYuvToJpeg(unsigned char *yuvBuffer,
                          int yuvSize,
                          int type,
                          int width,
                          int height,
                          int padding,
                          int quality,
                          unsigned char **jpgBuffer,
                          int &jpgSize);

private:

    /*
       * YUV420P:YV12 -> YU12,because libjpeg only surport YU12
       */
    bool yv12ToYu12(BYTE* yuv_buffer, int yuv_size, int width, int height, int padding);

    /*
     * NV12 -> YU12
     */
    bool nv12ToYu12(BYTE* yuv_buffer, int yuv_size, int width, int height, int padding);

    /**
     * NV21 -> YU12
     */
    int nv21ToYu12(BYTE *in, int width, int height);

    /**
     * YUYV -> YUV422P
     */
    int yuyvToYuv422P(BYTE *in, int width, int height);

    /**
     * YU12 -> Jpeg
     * @param yuv_buffer:yuv数据区
     * @param yuv_size:yuv大小
     * @param width:yuv宽度
     * @param height:yuv高度
     * @param quality:jpg压缩质量 (1-100)
     * @out jpg_buffer:输出的jpg数据
     * @out jpg_size :输出的jpg大小
     * @return :0为成功
     */
    int yu12ToJpeg(BYTE* yuv_buffer, int yuv_size, int width, int height, int padding, int quality,
                   BYTE** jpg_buffer, int& jpg_size, TJSAMP = TJSAMP_420);
};

其中 YuvToJpegUtil.cpp 如下:

#include <cstring>
#include <cstdint>
#include "YuvToJpegUtil.h"
#include "turbojpeg.h"

bool YuvToJpegUtil::convertYuvToJpeg(unsigned char *yuvBuffer, int yuvSize, int type, int width, int height, int padding,
                                    int quality, unsigned char **jpgBuffer, int &jpgSize) {

    // yu12 -> jpeg
    if (type == 0) {
        return yu12ToJpeg(yuvBuffer, yuvSize, width, height, padding, quality, jpgBuffer,
                          jpgSize);
    }

    // yv12 -> yu12
    // yu12 -> jpeg
    if (type == 1) {
        yv12ToYu12(yuvBuffer, yuvSize, width, height, padding);
        return yu12ToJpeg(yuvBuffer, yuvSize, width, height, padding, quality, jpgBuffer,
                          jpgSize);
    }

    // nv12 -> yu12
    // yu12 -> jpeg
    if (type == 2) {
        nv12ToYu12(yuvBuffer, yuvSize, width, height, padding);
        return yu12ToJpeg(yuvBuffer, yuvSize, width, height, padding, quality, jpgBuffer,
                          jpgSize);
    }

    // nv21 -> yu12
    // yu12 -> jpeg
    if (type == 3) {
        nv21ToYu12(yuvBuffer, width, height);
        int result = yu12ToJpeg(yuvBuffer, yuvSize, width, height, padding, quality,
                                jpgBuffer,
                                jpgSize);
        return result;
    }

    // yuyv -> yuv422p
    // yu12 -> jpeg
    if (type == 4) {
        yuyvToYuv422P(yuvBuffer, width, height);
        int result = yu12ToJpeg(yuvBuffer, yuvSize, width, height, padding, quality,
                                jpgBuffer,
                                jpgSize, TJSAMP_422);
        return result;
    }
}

/**
 * YUV420P:YV12 -> YU12
 */
bool YuvToJpegUtil::yv12ToYu12(BYTE *yuvBuffer, int yuvSize, int width, int height, int padding) {
    int uvLength = (yuvSize - width * height) / 2;
    BYTE *tempCache = new BYTE[uvLength];

    memcpy(tempCache, yuvBuffer + width * height, uvLength);
    memcpy(yuvBuffer + width * height, yuvBuffer + width * height + uvLength, uvLength);
    memcpy(yuvBuffer + width * height + uvLength, tempCache, uvLength);
    delete[] tempCache;
    tempCache = nullptr;
    return true;
}

/**
 * NV12 -> YU12
 */
bool YuvToJpegUtil::nv12ToYu12(BYTE *yuvBuffer, int yuvSize, int width, int height, int padding) {
    // 将yyyyyyuvuvuv 分离为 yyyuuuvvv
    // u 、v各占 1/6
    // y 独占 2/3
    // 创建存放UV的缓存
    int size = width * height * 3 / 2;
    BYTE *tempVBuffer = new BYTE[size / 6];
    // 第一个V的位置
    int uvIndex = 0;
    int vIndex = 0;
    int yEndPos = size * 2 / 3 + 1;
    for (int index = yEndPos; index < size; ++index) {
        // 偶数为 u
        if (index % 2 == 0) {
            // 当前的U 要替换  的位置
            int newUPos = yEndPos + uvIndex / 2;
            yuvBuffer[newUPos] = yuvBuffer[index];
        } else {
            *(tempVBuffer + vIndex) = yuvBuffer[index];
            vIndex++;
        }
        uvIndex++;
    }
    // 将v拼接到后面
    memcpy(yuvBuffer + size * 5 / 6, tempVBuffer, size / 6);
    delete[](tempVBuffer);
    return true;
}

/**
 * NV21 -> YU12
 */
int YuvToJpegUtil::nv21ToYu12(BYTE *in, int width, int height) {
    // 将yyyyyyvuvuvuvuvu 分离为 yyyuuuvvv
    // u 、v各占 1/6
    // y 独占 2/3
    // 创建存放UV的缓存
    int size = width * height * 3 / 2;
    BYTE *tempVBuffer = new BYTE[size / 6];
    // 第一个V的位置
    int uvIndex = 0;
    int vIndex = 0;
    int yEndPos = size * 2 / 3 + 1;
    for (int index = yEndPos; index < size; ++index) {
        // 偶数为 v
        if (index % 2 != 0) {
            // 当前的U 要替换  的位置
            int newUPos = yEndPos + uvIndex / 2;
            in[newUPos] = in[index];
        } else {
            *(tempVBuffer + vIndex) = in[index];
            vIndex++;
        }
        uvIndex++;
    }
    // 将v拼接到后面
    memcpy(in + size * 5 / 6, tempVBuffer, size / 6);
    delete[](tempVBuffer);
    return 0;
}

/**
 * YU12 -> Jpeg
 */
int YuvToJpegUtil::yu12ToJpeg(BYTE *yuvBuffer, int yuvSize, int width, int height, int padding,
                        int quality,
                        BYTE **jpgBuffer, int &jpgSize, TJSAMP TJSAMP_TYPE) {

    int subsample = TJSAMP_TYPE;

    tjhandle handle = NULL;
    int flags = 0;
    int need_size = 0;
    int ret = 0;

    handle = tjInitCompress();

    flags |= 0;

    need_size = tjBufSizeYUV2(width, padding, height, subsample);
    if (need_size != yuvSize) {
        return -1;
    }
    unsigned long retSize = 0;
    ret = tjCompressFromYUV(handle, yuvBuffer, width, padding, height, subsample,
                            jpgBuffer, &retSize, quality, flags);
    jpgSize = retSize;
    if (ret < 0) {
//        Log::info("压缩jpeg失败,错误信息:%s", tjGetErrorStr());
    }
    tjDestroy(handle);

    return ret;
}

/**
 * YUYV -> Yuv422P
 */
int YuvToJpegUtil::yuyvToYuv422P(BYTE *in, int width, int height) {
    // 将yuyv 分离为 yyyuuuvvv
    // 创建存放UV的缓存
    int size = width * height * 2;
    int halfOfSize = size / 2;
    BYTE *tempUVBuffer = new BYTE[halfOfSize];
    // 第一个U的位置
    BYTE *UBuffer = tempUVBuffer;
    // 第一个V的位置
    BYTE *VBuffer = tempUVBuffer + halfOfSize / 2;
    bool isU = true;
    int uIndex = 0;
    int vIndex = 0;
    for (int index = 0; index < size; ++index) {
        // 偶数为 Y
        if (index % 2 == 0) {
            // 当前的Y 要替换 index/2 的位置
            in[index / 2] = in[index];
        } else {
            // U、V交替
            if (isU) {
                isU = false;
                *(UBuffer + uIndex) = in[index];
                uIndex++;
            } else {
                *(VBuffer + vIndex) = in[index];
                isU = true;
                vIndex++;
            }
        }
    }
    // 将uv拼接到后面
    memcpy(in + halfOfSize, tempUVBuffer, halfOfSize);
    delete[](tempUVBuffer);
    return 0;
}

接下来,是调用方使用,代码如下:

#include "YuvToJpegUtil.h"

void testYuvToJpeg(int quality){

    YuvToJpegUtil *jpgUtil = new YuvToJpegUtil();
    uint8_t *jpgBuffer = new uint8_t[1024 * 1024];
    int jpgSize = 1024 * 1024;
    int yuvSize = 1920 * 1080 * 3 / 2;
    unsigned char *yuvBuffer = new unsigned char[yuvSize];
    FILE *fp_in = fopen(
            "/sdcard/1614138720028814.yuv",
            "rb+");
    if (fp_in != NULL) {
        fread(yuvBuffer, yuvSize, 1, fp_in);
    } else {
        std::cout<<"找不到yuv文件"<<std::endl;
        return;
    }
    fclose(fp_in);
    jpgUtil->convertYuvToJpeg(yuvBuffer, yuvSize, 0, 1920, 1080, 4, quality, &jpgBuffer, jpgSize);
    std::string path = "/sdcard/1614138720028814_";
    path = path+ std::to_string(quality)+".jpg";
    FILE *fp_out = NULL;
    if ((fp_out = fopen(
            path.c_str(),
            "wb+")) == NULL) {
        std::cout<<"文件创建失败"<<std::endl;
    } else {
        fwrite(jpgBuffer, jpgSize, 1, fp_out);
        fclose(fp_out);
        std::cout<<"文件创建成功"<<std::endl;
    }

    delete[] jpgBuffer;
    delete[] yuvBuffer;
}

int main() {
    testYuvToJpeg(80);
    testYuvToJpeg(100);
    return 0;
}

配置文件 CMakeList.txt 如下:

...

# 第一步
include_directories(
        ...
        thirdParty/libjpeg-turbo/Headers
)

# 第二步
add_subdirectory("thirdParty/libjpeg-turbo")


# 第三步, LibJpeg_turbo_static 请参照下载文件中编译好的静态库
target_link_libraries(
        demo
        LibJpeg_turbo_static 
        )