-
Notifications
You must be signed in to change notification settings - Fork 53
09. MJ2 (Motion JPEG 2000)
Grok supports MJ2 (Motion JPEG 2000) containers for multi-frame compress and decompress workflows. An MJ2 file wraps multiple JPEG 2000 codestreams inside an ISO base media file format (ISOBMFF) container, as defined by ISO/IEC 15444-3.
Use --batch-src (-y) to specify a directory of input images along with
-o to specify the MJ2 output file:
grk_compress -y /path/to/frames/ -o output.mj2
All supported input image formats (PNG, BMP, TIFF, PNM, PGX,
RAW, RAWL, JPG) can be used as source frames. Images are sorted
alphabetically and compressed sequentially into the MJ2 container.
All standard JPEG 2000 compression parameters apply to each frame
(e.g., -r for compression ratio, -q for quality, -I for
irreversible DWT, -n for resolutions, etc.).
Example with lossy compression:
grk_compress -y /path/to/frames/ -o output.mj2 -r 20
grk_decompress -i input.mj2 -o frame.png
All frames are automatically extracted and saved as numbered output files:
frame_000.png, frame_001.png, frame_002.png, etc.
Any supported output format can be used (PNG, BMP, TIFF, PNM,
PGX, RAW, RAWL, JPG).
Grok provides a set of C API functions for programmatic multi-frame compress and decompress workflows. These are useful for applications that need to create or read MJ2 files without the command-line tools.
uint32_t grk_decompress_num_samples(grk_object* codec);Returns the number of samples (frames) in the container. For single-image
formats (JP2, J2K) this returns 1. For MJ2 it returns the number of
video samples.
bool grk_decompress_sample(grk_object* codec, uint32_t sample_index);Decompresses a single sample (frame) by 0-based index. Must be called
after grk_decompress() has decompressed the first frame (index 0).
grk_image* grk_decompress_get_sample_image(grk_object* codec, uint32_t sample_index);Returns the decompressed image for a specific sample. Returns NULL if
the sample has not been decompressed yet.
grk_image* grk_decompress_get_sample_tile_image(grk_object* codec,
uint32_t sample_index,
uint16_t tile_index);Returns a specific tile from a decompressed sample. Useful when frames
are encoded with multiple tiles. The tile_index is row-major within
the tile grid: tile_y * num_tile_cols + tile_x. The sample must have
been decompressed first via grk_decompress() or grk_decompress_sample().
#include "grok.h"
grk_initialize(NULL, 0, NULL);
grk_decompress_parameters params = {};
grk_stream_params stream = {};
safe_strcpy(stream.file, "input.mj2");
grk_object* codec = grk_decompress_init(&stream, ¶ms);
grk_header_info header = {};
grk_decompress_read_header(codec, &header);
// Decompress first frame (index 0)
grk_decompress(codec, NULL);
uint32_t num_frames = grk_decompress_num_samples(codec);
// Decompress remaining frames
for (uint32_t i = 1; i < num_frames; i++)
grk_decompress_sample(codec, i);
// Access decompressed images
for (uint32_t i = 0; i < num_frames; i++) {
grk_image* img = grk_decompress_get_sample_image(codec, i);
// Process img->comps[c].data ...
}
grk_object_unref(codec);
grk_deinitialize();uint64_t grk_compress_frame(grk_object* codec, grk_image* image,
grk_plugin_tile* tile);Compresses an additional frame into the MJ2 container. The first frame
is compressed with grk_compress(). Subsequent frames use this function.
Returns the number of bytes written, or 0 on failure. Pass NULL for
tile.
bool grk_compress_finish(grk_object* codec);Finalizes the MJ2 container (writes the moov box and other required
metadata). Must be called after all frames have been compressed.
uint64_t grk_compress_get_compressed_length(grk_object* codec);Returns the total number of bytes written to the output stream or buffer. Useful when compressing to an in-memory buffer.
#include "grok.h"
grk_initialize(NULL, 0, NULL);
grk_cparameters params = {};
grk_compress_set_default_params(¶ms);
params.cod_format = GRK_FMT_MJ2;
grk_stream_params stream = {};
safe_strcpy(stream.file, "output.mj2");
// Create first frame image (grk_image_new)
grk_image* frame0 = /* ... create image ... */;
grk_object* codec = grk_compress_init(&stream, ¶ms, frame0);
// Compress first frame
grk_compress(codec, NULL);
// Compress additional frames
for (uint32_t i = 1; i < num_frames; i++) {
grk_image* frame = /* ... create image ... */;
grk_compress_frame(codec, frame, NULL);
grk_object_unref(&frame->obj);
}
// Finalize MJ2 container
grk_compress_finish(codec);
uint64_t total_bytes = grk_compress_get_compressed_length(codec);
grk_object_unref(codec);
grk_deinitialize();Both compress and decompress support in-memory buffer I/O via the
grk_stream_params struct. Set buf and buf_len instead of file:
// Compress to buffer
uint8_t buffer[1024 * 1024];
grk_stream_params stream = {};
stream.buf = buffer;
stream.buf_len = sizeof(buffer);
grk_object* codec = grk_compress_init(&stream, ¶ms, image);
grk_compress(codec, NULL);
// ... compress additional frames ...
grk_compress_finish(codec);
uint64_t compressed_size = grk_compress_get_compressed_length(codec);
// buffer[0..compressed_size-1] contains the MJ2 data
// Decompress from buffer
grk_stream_params dec_stream = {};
dec_stream.buf = buffer;
dec_stream.buf_len = compressed_size;
grk_object* dec_codec = grk_decompress_init(&dec_stream, &dec_params);
// ... decompress as usual ...- All frames in an MJ2 file must have the same dimensions and component structure (number of components, bit depth, color space).
- Standard JPEG 2000 compression parameters (quality layers, DWT type, precincts, code block size, etc.) apply uniformly to all frames.
- The MCT (Multi-Component Transform) is automatically applied when the image has 3 or more components.