image-processingjpegyuvmjpeglibyuv

Example usage of a libyuv API MJPGToI420()


I'm trying to use a libyuv API, more specifically MJPGToI420().

I want to first take a jpeg image as input to MJPGToI420(), the signature of which is below:

int MJPGToI420(const uint8_t* sample,
               size_t sample_size,
               uint8_t* dst_y,
               int dst_stride_y,
               uint8_t* dst_u,
               int dst_stride_u,
               uint8_t* dst_v,
               int dst_stride_v,
               int src_width,
               int src_height,
               int dst_width,
               int dst_height);

Then, I want to allocate space for the dst_y, dst_u, and dst_v pointers. However, I don't know how much space to allocate for them. I'm also confused about what the strides should be, i.e., what the parameters dst_stride_y, dst_stride_u and dst_stride_v should be.

Would really appreciate any pointers in the right direction.

EDIT: Here's a snippet of code from the libyuv source unit tests that uses this function. However, the test returns 1 which is failure of the function as the intended behavior. The test also just uses zeroes for the data, instead of an actual MJPG file.

TEST_F(LibYUVConvertTest, MJPGToI420) {
  const int kOff = 10;
  const int kMinJpeg = 64;
  const int kImageSize = benchmark_width_ * benchmark_height_ >= kMinJpeg
                             ? benchmark_width_ * benchmark_height_
                             : kMinJpeg;
  const int kSize = kImageSize + kOff;
  align_buffer_page_end(orig_pixels, kSize);
  align_buffer_page_end(dst_y_opt, benchmark_width_ * benchmark_height_);
  align_buffer_page_end(dst_u_opt, SUBSAMPLE(benchmark_width_, 2) *
                                       SUBSAMPLE(benchmark_height_, 2));
  align_buffer_page_end(dst_v_opt, SUBSAMPLE(benchmark_width_, 2) *
                                       SUBSAMPLE(benchmark_height_, 2));

  // EOI, SOI to make MJPG appear valid.
  memset(orig_pixels, 0, kSize);
  orig_pixels[0] = 0xff;
  orig_pixels[1] = 0xd8;  // SOI.
  orig_pixels[kSize - kOff + 0] = 0xff;
  orig_pixels[kSize - kOff + 1] = 0xd9;  // EOI.

  for (int times = 0; times < benchmark_iterations_; ++times) {
    int ret =
        MJPGToI420(orig_pixels, kSize, dst_y_opt, benchmark_width_, dst_u_opt,
                   SUBSAMPLE(benchmark_width_, 2), dst_v_opt,
                   SUBSAMPLE(benchmark_width_, 2), benchmark_width_,
                   benchmark_height_, benchmark_width_, benchmark_height_);
    // Expect failure because image is not really valid.
    EXPECT_EQ(1, ret);
  }

  free_aligned_buffer_page_end(dst_y_opt);
  free_aligned_buffer_page_end(dst_u_opt);
  free_aligned_buffer_page_end(dst_v_opt);
  free_aligned_buffer_page_end(orig_pixels);
}

EDIT 2: Furthermore, this is what I've tried, however, the end yuv files are not even viewable in a yuv viewer (created using the buffers dst_u_opt and dst_y_opt), which makes me believe there might be something that I'm messing up with the function:

int convertMJPGToI420() {

    auto fileSize = filesize(IMG_NAME);

    // load image into memory
    uint8_t* my_img = (uint8_t*) calloc(fileSize, 1);
    std::ifstream fin(IMG_NAME, ios::in | ios::binary);
    fin.read(reinterpret_cast<char*>(my_img), fileSize);

    // exif data offset 
    // This is the size of the exif data
    const int kOff = 4096;

    // 4k image is being sent in
    int benchmark_width_ = 3840;
    int benchmark_height_ = 2160;

    const int kSize = fileSize;

    // align_buffer_page_end is a macro (look at link posted for unit tests above)
    // I'm not sure if the size allocation for these is correct
    // I have tried to model it based off the example
    align_buffer_page_end(orig_pixels, kSize);
    align_buffer_page_end(dst_y_opt, benchmark_width_ * benchmark_height_);
    align_buffer_page_end(dst_u_opt, SUBSAMPLE(benchmark_width_, 2) * 
                                        SUBSAMPLE(benchmark_height_, 2));
    align_buffer_page_end(dst_v_opt, SUBSAMPLE(benchmark_width_, 2) * 
                                        SUBSAMPLE(benchmark_height_, 2));

    // EOI, SOI to make MJPG appear valid
    memset(orig_pixels, 0, kSize);
    orig_pixels[0] = 0xff;
    orig_pixels[1] = 0xd8; // SOI

    memcpy(orig_pixels + 2, my_img, kSize - kOff - 3);

    orig_pixels[kSize - kOff + 0] = 0xff;
    orig_pixels[kSize - kOff + 1] = 0xd9; // EOI

    // using async as this function might be ansynchronous
    std::future<int> ret = std::async(libyuv::MJPGToI420, orig_pixels, kSize, dst_y_opt, benchmark_width_,
                        dst_u_opt, SUBSAMPLE(benchmark_width_, 2),
                        dst_v_opt, SUBSAMPLE(benchmark_width_, 2), 
                        benchmark_width_, benchmark_height_, 
                        benchmark_width_, benchmark_height_);

    ret.wait();

    // ret is always one, which means there was a failure
    if(ret.get() == 0) {
        cout << "return value was zero" << endl;
    } else {
        cout << "return value was one" << endl;
    }

    FILE* file = fopen("/data/dst_u_opt", "wb");
    fwrite(dst_y_opt, 1, SUBSAMPLE(benchmark_width_, 2) * SUBSAMPLE(benchmark_height_, 2) , file);
    fclose(file);

    file = fopen("/data/dst_v_opt", "wb");
    fwrite(dst_y_opt, 1, SUBSAMPLE(benchmark_width_, 2) *  SUBSAMPLE(benchmark_height_, 2), file);
    fclose(file);

    free_aligned_buffer_page_end(dst_y_opt);
    free_aligned_buffer_page_end(dst_u_opt);
    free_aligned_buffer_page_end(dst_v_opt);
    free_aligned_buffer_page_end(orig_pixels);

    return 0;
}

Solution

  • You'll need to know the width and height of the jpeg.

    I420 is a 420 sub-sampled YUV. The Y plane is width * height in bytes. The dst_stride_y value is width e.g.

    char* dst_y = malloc(width * height);
    

    The U and V planes are half width and height. To handle odd sizes you should round up.

    dst_stride_u = (width + 1) / 2;
    dst_stride_v = (width + 1) / 2;
    

    The u and v planes are ((width + 1) / 2) * ((height + 1) / 2) bytes.

    char* dst_u = malloc(((width + 1) / 2) * ((height + 1) / 2));
    char* dst_y = malloc(((width + 1) / 2) * ((height + 1) / 2));
    

    If you'd like to file an issue, including better documentation, post it here: https://bugs.chromium.org/p/libyuv/issues/list