C API 示例#

本节将介绍如何通过十个快速步骤使用 nvImageCodec C API 编码或解码图像。API 详细信息将在下一节中介绍。

注意

所有 nvImageCodec API 都应返回 NVIMGCODEC_STATUS_SUCCESS。否则结果可能无效。

单张图像解码#

  1. 1. 初始化库并创建库实例句柄

nvimgcodecInstance_t instance;
nvimgcodecInstanceCreateInfo_t instance_create_info{NVIMGCODEC_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, sizeof(nvimgcodecInstanceCreateInfo_t), 0};
instance_create_info.load_builtin_modules = 1;
instance_create_info.load_extension_modules = 1;
instance_create_info.create_debug_messenger = 1;

nvimgcodecInstanceCreate(&instance, &instance_create_info);

2. 创建代码流,用于抽象压缩比特流的源。让我们从文件创建 nvimgcodecCodeStream_t 开始。

nvimgcodecCodeStream_t code_stream;
nvimgcodecCodeStreamCreateFromFile(instance, &code_stream, "./test.jpg");

3. 通过从代码流获取 nvimgcodecImageInfo_t 来获取有关压缩图像的图像信息。此操作将对压缩比特流进行最小限度的解析,但不会对其进行解码。

nvimgcodecImageInfo_t input_image_info{NVIMGCODEC_STRUCTURE_TYPE_IMAGE_INFO, sizeof(nvimgcodecImageInfo_t), 0};
nvimgcodecCodeStreamGetImageInfo(code_stream, &input_image_info);

std::cout << "Input image info: " << std::endl;
std::cout << "\t - width:" << input_image_info.plane_info[0].width << std::endl;
std::cout << "\t - height:" << input_image_info.plane_info[0].height << std::endl;
std::cout << "\t - components:" << input_image_info.num_planes << std::endl;
std::cout << "\t - codec:" << input_image_info.codec_name << std::endl;

4. 在设备上分配输出内存,并指定请求的输出图像信息。下面的代码片段演示了 nvimgcodecImageInfo_t 的初始化,用于 8 位、SRGB、3 通道、平面格式、无色度二次采样以及步幅设备内存中的输出缓冲区。

nvimgcodecImageInfo_t output_image_info(input_image_info);
output_image_info.sample_format = NVIMGCODEC_SAMPLEFORMAT_P_RGB;
output_image_info.color_spec = NVIMGCODEC_COLORSPEC_SRGB;
output_image_info.chroma_subsampling = NVIMGCODEC_SAMPLING_NONE;
output_image_info.num_planes = 3;
output_image_info.buffer_kind = NVIMGCODEC_IMAGE_BUFFER_KIND_STRIDED_DEVICE;

auto sample_type = output_image_info.plane_info[0].sample_type;
int bytes_per_element = static_cast<unsigned int>(sample_type)>> (8+3);
size_t device_pitch_in_bytes = input_image_info.plane_info[0].width * bytes_per_element;

for (uint32_t c = 0; c <  output_image_info.num_planes; ++c) {
   output_image_info.plane_info[c].height = input_image_info.plane_info[0].height;
   output_image_info.plane_info[c].width = input_image_info.plane_info[0].width;
   output_image_info.plane_info[c].row_stride = device_pitch_in_bytes;
}

output_image_info.buffer_size = output_image_info.plane_info[0].row_stride * output_image_info.plane_info[0].height * output_image_info.num_planes;
output_image_info.cuda_stream = 0; // It is possible to assign cuda stream which will be used for synchronization. Here we assume it is default stream.

cudaMalloc(&output_image_info.buffer,  output_image_info.buffer_size);

5. 准备好请求的输出图像格式信息后,我们可以创建不透明的 nvimgcodecImage_t 类型句柄,以表示我们的解码图像。

nvimgcodecImage_t image;
nvimgcodecImageCreate(instance, &image, &image_info);
  1. 6. 创建解码器。

可以在创建解码器函数时传递执行参数,我们可以在其中指定执行解码的设备 ID。如果我们想将一些额外的选项传递给所有或特定的解码器插件,我们可以通过字符串来实现,该字符串包含可选的空格分隔的参数列表,用于特定解码器,格式为:“<decoder_id>:<parameter_name>=<parameter_value>”。例如 “nvjpeg:fancy_upsampling=1”。如果我们跳过 <decoder_id>,如下面的代码片段所示,则该选项可以被许多解码器插件解释。

nvimgcodecExecutionParams_t exec_params{NVIMGCODEC_STRUCTURE_TYPE_EXECUTION_PARAMS, sizeof(nvimgcodecExecutionParams_t), 0};
exec_params.device_id = NVIMGCODEC_DEVICE_CURRENT;
nvimgcodecDecoder_t decoder;
std::string dec_options{":fancy_upsampling=0"};
nvimgcodecDecoderCreate(instance, &decoder, &exec_params, dec_options.c_str());

7. 准备解码参数。在下面的代码片段中,我们仅设置允许应用 exif 方向的标志。

nvimgcodecDecodeParams_t decode_params{NVIMGCODEC_STRUCTURE_TYPE_DECODE_PARAMS, sizeof(nvimgcodecDecodeParams_t), 0};
decode_params.apply_exif_orientation = 1;
  1. 8. 安排解码。

nvimgcodecFuture_t decode_future;
nvimgcodecDecoderDecode(decoder, &code_stream, &image, 1, &decode_params, &decode_future);
  1. 9. 等待解码完成并检查其状态。

nvimgcodecImageInfo_t 中的字段之一是 cudaStream_t,库使用它来发出异步 CUDA 调用。为了完成解码过程,您需要调用 cudaDeviceSynchronize(),因为 nvimgcodecDecoderDecode 相对于主机是异步的。或者,您可以使用 cudaStreamSynchronize 与特定的 CUDA 流同步。此外,如果您想在 GPU 上处理解码后的图像,您可以跳过此处的同步,并使用 nvimgcodecImageInfo_t 中定义的 CUDA 流来安排在 GPU 上进行进一步的图像处理,这将在解码后发生。

size_t status_size;
nvimgcodecProcessingStatus_t decode_status;
nvimgcodecFutureGetProcessingStatus(decode_future, &decode_status, &status_size);
cudaDeviceSynchronize(); // makes GPU wait until all decoding is finished
if (decode_status != NVIMGCODEC_PROCESSING_STATUS_SUCCESS) {
    std::cerr << "Error: Something went wrong during decoding - processing status: " << decode_status << std::endl;
}
nvimgcodecFutureDestroy(decode_future);
  1. 10. 清理

cudaFree(image_info.buffer);

nvimgcodecImageDestroy(image);
nvimgcodecDecoderDestroy(decoder);
nvimgcodecCodeStreamDestroy(code_stream);
nvimgcodecInstanceDestroy(instance);

单张图像编码#

库期望输入图像位于设备或主机内存中,压缩后的输出将始终写入主机内存。

  1. 1. 初始化库并创建库实例句柄

nvimgcodecInstance_t instance;
nvimgcodecInstanceCreateInfo_t instance_create_info{NVIMGCODEC_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, sizeof(nvimgcodecInstanceCreateInfo_t), 0};
instance_create_info.load_builtin_modules = 1;
instance_create_info.load_extension_modules = 1;
instance_create_info.create_debug_messenger = 1;

nvimgcodecInstanceCreate(&instance, &instance_create_info);
  1. 1. 准备输入图像信息。

下面的代码片段演示了 nvimgcodecImageInfo_t 的初始化,并具有以下假设
  • 采样类型是无符号 8 位整数

  • 采样格式是平面 RGB,因此是 3 个平面,每个平面有 1 个通道(颜色分量)

  • 图像存储在主机内存中

nvimgcodecImageInfo_t input_image_info{NVIMGCODEC_STRUCTURE_TYPE_IMAGE_INFO, sizeof(nvimgcodecImageInfo_t), 0};

input_image_info.num_planes = 3;
input_image_info.plane_info[0].height = /* TODO assign image height */;
input_image_info.plane_info[0].width =  /* TODO assign image width */;
input_image_info.plane_info[0].num_channels = 1;


input_image_info.color_spec = NVIMGCODEC_COLORSPEC_SRGB;
input_image_info.sample_format = NVIMGCODEC_SAMPLEFORMAT_P_RGB;
input_image_info.chroma_subsampling = NVIMGCODEC_SAMPLING_444;

auto sample_type = NVIMGCODEC_SAMPLE_DATA_TYPE_UINT8;

// For general case, having sample type,  we can calculate bytes per element using formula static_cast<unsigned int>(sample_type) >> (8 + 3);
// so shift by 8 since 8..15 bits represents type bitdepth,  then shift by 3 to convert to # bytes
// here we can simple assign 1 as we assumed type is uint8
int bytes_per_element =  1;

int pitch_in_bytes = input_image_info.plane_info[0].width * input_image_info.plane_info[0].num_channels * bytes_per_element;

size_t buffer_size = 0;
for (size_t p = 0; p < input_image_info.num_planes; p++) {
    input_image_info.plane_info[p].width = input_image_info.plane_info[0].width;
    input_image_info.plane_info[p].height = input_image_info.plane_info[0].height;
    input_image_info.plane_info[p].row_stride = pitch_in_bytes;
    input_image_info.plane_info[p].sample_type = sample_type;
    input_image_info.plane_info[p].num_channels = input_image_info.plane_info[0].num_channels;
    input_image_info.plane_info[p].precision = 8;
    buffer_size += input_image_info.plane_info[p].row_stride * input_image_info.plane_info[0].height;
}

input_image_info.buffer_kind = NVIMGCODEC_IMAGE_BUFFER_KIND_STRIDED_HOST; //or NVIMGCODEC_IMAGE_BUFFER_KIND_STRIDED_DEVICE if image is already in GPU memory
input_image_info.buffer = /* TODO assign pointer to host memory where the image data in planar format are stored */;
input_image_info.buffer_size = buffer_size;
input_image_info.cuda_stream = 0; // It is possible to assign cuda stream which will be used for synchronization. Here we assume it is default stream.
  1. 2. 创建不透明的 nvimgcodecImage_t 类型句柄,以表示我们的输入图像。

nvimgcodecImage_t input_image;
nvimgcodecImageCreate(instance, &input_image, &input_image_info);
  1. 3. 准备输出(压缩)图像信息。此信息将在下一步中用于创建代码流

首先基于输入图像信息进行初始化,以便我们已经填充了图像大小、采样类型和其他信息。然后我们可以指定要用于编码的编解码器。一般来说,您可以使用编解码器名称,具体取决于您安装的扩展。默认情况下,在当前版本的 nvImageCodec 中,带有 jpegjpeg2k GPU 加速编码器。您还可以使用 bmppnm 编码器的基本示例 CPU 实现。这里我们选择 jpeg 编解码器。我们还可以为 jpeg 特定图像信息创建结构,我们可以在其中指定编码,例如在下面的代码片段中选择渐进式 dct Huffman。为了使用 jpeg 特定信息(此处为编码类型)扩展输出(压缩)图像信息,我们必须将 jpeg 图像信息结构链接到输出图像信息。

nvimgcodecImageInfo_t out_image_info(input_image_info);

strcpy(out_image_info.codec_name, "jpeg");

nvimgcodecJpegImageInfo_t jpeg_image_info{NVIMGCODEC_STRUCTURE_TYPE_JPEG_IMAGE_INFO, sizeof(nvimgcodecJpegImageInfo_t), 0};
jpeg_image_info.encoding = NVIMGCODEC_JPEG_ENCODING_PROGRESSIVE_DCT_HUFFMAN;
image_info.struct_next = &jpeg_image_info;
  1. 4. 创建输出代码流。

nvimgcodecCodeStream_t output_code_stream;
nvimgcodecCodeStreamCreateToFile(instance, &output_code_stream, "out.jpg", &out_image_info);
  1. 5. 创建编码器。

可以在创建编码器函数时传递执行参数,我们可以在其中指定执行编码的设备 ID。如果我们想将一些额外的选项传递给所有或特定的编码器插件,我们可以通过字符串来实现,该字符串包含可选的空格分隔的参数列表,用于特定编码器,格式为:“<encoder_id>:<parameter_name>=<parameter_value>”。

nvimgcodecExecutionParams_t exec_params{NVIMGCODEC_STRUCTURE_TYPE_EXECUTION_PARAMS, sizeof(nvimgcodecExecutionParams_t), 0};
exec_params.device_id = NVIMGCODEC_DEVICE_CURRENT;

nvimgcodecEncoder_t encoder;
nvimgcodecEncoderCreate(instance, &encoder, &exec_params, nullptr);
  1. 6. 准备编码参数。

7. 对于有损压缩,我们需要指定质量。

注意

8. 对于 JPEG2000,我们通过提供目标 psnr 来指定质量,例如 encode_params.target_psnr = 35;

nvimgcodecEncodeParams_t encode_params{NVIMGCODEC_STRUCTURE_TYPE_ENCODE_PARAMS, sizeof(nvimgcodecEncodeParams_t), 0};

encode_params.quality = 75;
  1. 9. 安排编码。

nvimgcodecFuture_t encode_future;
nvimgcodecEncoderEncode(encoder, &input_image, &output_code_stream, 1, &encode_params, &encode_future);
  1. 10. 等待编码完成并读取处理状态。

size_t status_size;
nvimgcodecProcessingStatus_t encode_status;
nvimgcodecFutureGetProcessingStatus(encode_future, &encode_status, &status_size);
cudaDeviceSynchronize(); // makes GPU wait until all encoding is finished
if (encode_status != NVIMGCODEC_PROCESSING_STATUS_SUCCESS) {
    std::cerr << "Error: Something went wrong during encoding" << std::endl;
}
nvimgcodecFutureDestroy(encode_future);
  1. 10. 清理

nvimgcodecEncoderDestroy(encoder);
nvimgcodecCodeStreamDestroy(output_code_stream);
nvimgcodecImageDestroy(input_image);
nvimgcodecInstanceDestroy(instance);