| /* GStreamer JPEG parser test utility | |
| * Copyright (C) 2015 Tim-Philipp Müller <tim@centricular.com> | |
| * | |
| * This library is free software; you can redistribute it and/or | |
| * modify it under the terms of the GNU Library General Public | |
| * License as published by the Free Software Foundation; either | |
| * version 2 of the License, or (at your option) any later version. | |
| * | |
| * This library is distributed in the hope that it will be useful, | |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
| * Library General Public License for more details. | |
| * | |
| * You should have received a copy of the GNU Library General Public | |
| * License along with this library; if not, write to the | |
| * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, | |
| * Boston, MA 02110-1301, USA. | |
| */ | |
| #ifdef HAVE_CONFIG_H | |
| #include "config.h" | |
| #endif | |
| #include <gst/gst.h> | |
| #include <gst/codecparsers/gstjpegparser.h> | |
| #include <stdlib.h> | |
| static GstBuffer *app_segments[16]; /* NULL */ | |
| static const gchar * | |
| get_marker_name (guint8 marker) | |
| { | |
| switch (marker) { | |
| case GST_JPEG_MARKER_SOF0: | |
| return "SOF (Baseline)"; | |
| case GST_JPEG_MARKER_SOF1: | |
| return "SOF (Extended Sequential, Huffman)"; | |
| case GST_JPEG_MARKER_SOF2: | |
| return "SOF (Extended Progressive, Huffman)"; | |
| case GST_JPEG_MARKER_SOF3: | |
| return "SOF (Lossless, Huffman)"; | |
| case GST_JPEG_MARKER_SOF5: | |
| return "SOF (Differential Sequential, Huffman)"; | |
| case GST_JPEG_MARKER_SOF6: | |
| return "SOF (Differential Progressive, Huffman)"; | |
| case GST_JPEG_MARKER_SOF7: | |
| return "SOF (Differential Lossless, Huffman)"; | |
| case GST_JPEG_MARKER_SOF9: | |
| return "SOF (Extended Sequential, Arithmetic)"; | |
| case GST_JPEG_MARKER_SOF10: | |
| return "SOF (Progressive, Arithmetic)"; | |
| case GST_JPEG_MARKER_SOF11: | |
| return "SOF (Lossless, Arithmetic)"; | |
| case GST_JPEG_MARKER_SOF13: | |
| return "SOF (Differential Sequential, Arithmetic)"; | |
| case GST_JPEG_MARKER_SOF14: | |
| return "SOF (Differential Progressive, Arithmetic)"; | |
| case GST_JPEG_MARKER_SOF15: | |
| return "SOF (Differential Lossless, Arithmetic)"; | |
| case GST_JPEG_MARKER_DHT: | |
| return "DHT"; | |
| case GST_JPEG_MARKER_DAC: | |
| return "DAC"; | |
| case GST_JPEG_MARKER_SOI: | |
| return "SOI"; | |
| case GST_JPEG_MARKER_EOI: | |
| return "EOI"; | |
| case GST_JPEG_MARKER_SOS: | |
| return "SOS"; | |
| case GST_JPEG_MARKER_DQT: | |
| return "DQT"; | |
| case GST_JPEG_MARKER_DNL: | |
| return "DNL"; | |
| case GST_JPEG_MARKER_DRI: | |
| return "DRI"; | |
| case GST_JPEG_MARKER_APP0: | |
| return "APP0"; | |
| case GST_JPEG_MARKER_APP1: | |
| return "APP1"; | |
| case GST_JPEG_MARKER_APP2: | |
| return "APP2"; | |
| case GST_JPEG_MARKER_APP3: | |
| return "APP3"; | |
| case GST_JPEG_MARKER_APP4: | |
| return "APP4"; | |
| case GST_JPEG_MARKER_APP5: | |
| return "APP5"; | |
| case GST_JPEG_MARKER_APP6: | |
| return "APP6"; | |
| case GST_JPEG_MARKER_APP7: | |
| return "APP7"; | |
| case GST_JPEG_MARKER_APP8: | |
| return "APP8"; | |
| case GST_JPEG_MARKER_APP9: | |
| return "APP9"; | |
| case GST_JPEG_MARKER_APP10: | |
| return "APP10"; | |
| case GST_JPEG_MARKER_APP11: | |
| return "APP11"; | |
| case GST_JPEG_MARKER_APP12: | |
| return "APP12"; | |
| case GST_JPEG_MARKER_APP13: | |
| return "APP13"; | |
| case GST_JPEG_MARKER_APP14: | |
| return "APP14"; | |
| case GST_JPEG_MARKER_APP15: | |
| return "APP15"; | |
| case GST_JPEG_MARKER_COM: | |
| return "COM"; | |
| default: | |
| if (marker > GST_JPEG_MARKER_RST_MIN && marker < GST_JPEG_MARKER_RST_MAX) | |
| return "RST"; | |
| break; | |
| } | |
| return "???"; | |
| } | |
| static gboolean | |
| parse_jpeg_segment (GstJpegSegment * segment) | |
| { | |
| switch (segment->marker) { | |
| case GST_JPEG_MARKER_SOF0: | |
| case GST_JPEG_MARKER_SOF1: | |
| case GST_JPEG_MARKER_SOF2: | |
| case GST_JPEG_MARKER_SOF3: | |
| case GST_JPEG_MARKER_SOF9: | |
| case GST_JPEG_MARKER_SOF10: | |
| case GST_JPEG_MARKER_SOF11:{ | |
| GstJpegFrameHdr hdr; | |
| int i; | |
| if (!gst_jpeg_segment_parse_frame_header (segment, &hdr)) { | |
| g_printerr ("Failed to parse frame header!\n"); | |
| return FALSE; | |
| } | |
| g_print ("\t\twidth x height = %u x %u\n", hdr.width, hdr.height); | |
| g_print ("\t\tsample precision = %u\n", hdr.sample_precision); | |
| g_print ("\t\tnum components = %u\n", hdr.num_components); | |
| for (i = 0; i < hdr.num_components; ++i) { | |
| g_print ("\t\t%d: id=%d, h=%d, v=%d, qts=%d\n", i, | |
| hdr.components[i].identifier, hdr.components[i].horizontal_factor, | |
| hdr.components[i].vertical_factor, | |
| hdr.components[i].quant_table_selector); | |
| } | |
| break; | |
| } | |
| case GST_JPEG_MARKER_DHT:{ | |
| GstJpegHuffmanTables ht; | |
| if (!gst_jpeg_segment_parse_huffman_table (segment, &ht)) { | |
| g_printerr ("Failed to parse huffman table!\n"); | |
| return FALSE; | |
| } | |
| break; | |
| } | |
| case GST_JPEG_MARKER_DQT:{ | |
| GstJpegQuantTables qt; | |
| if (!gst_jpeg_segment_parse_quantization_table (segment, &qt)) { | |
| g_printerr ("Failed to parse quantization table!\n"); | |
| return FALSE; | |
| } | |
| break; | |
| } | |
| case GST_JPEG_MARKER_SOS:{ | |
| GstJpegScanHdr hdr; | |
| int i; | |
| if (!gst_jpeg_segment_parse_scan_header (segment, &hdr)) { | |
| g_printerr ("Failed to parse scan header!\n"); | |
| return FALSE; | |
| } | |
| g_print ("\t\tnum components = %u\n", hdr.num_components); | |
| for (i = 0; i < hdr.num_components; ++i) { | |
| g_print ("\t\t %d: cs=%d, dcs=%d, acs=%d\n", i, | |
| hdr.components[i].component_selector, | |
| hdr.components[i].dc_selector, hdr.components[i].ac_selector); | |
| } | |
| } | |
| case GST_JPEG_MARKER_COM: | |
| /* gst_util_dump_mem (segment->data + segment->offset, segment->size); */ | |
| break; | |
| default: | |
| if (segment->marker >= GST_JPEG_MARKER_APP_MIN | |
| && segment->marker <= GST_JPEG_MARKER_APP_MAX) { | |
| guint n = segment->marker - GST_JPEG_MARKER_APP_MIN; | |
| if (app_segments[n] == NULL) | |
| app_segments[n] = gst_buffer_new (); | |
| gst_buffer_append_memory (app_segments[n], | |
| gst_memory_new_wrapped (GST_MEMORY_FLAG_READONLY, | |
| (guint8 *) segment->data + segment->offset, | |
| segment->size, 0, segment->size, NULL, NULL)); | |
| } | |
| break; | |
| } | |
| return TRUE; | |
| } | |
| static void | |
| parse_jpeg (const guint8 * data, gsize size) | |
| { | |
| GstJpegSegment segment; | |
| guint offset = 0; | |
| while (gst_jpeg_parse (&segment, data, size, offset)) { | |
| if (segment.offset > offset + 2) | |
| g_print (" skipped %u bytes\n", (guint) segment.offset - offset - 2); | |
| g_print ("%6d bytes at offset %-8u : %s\n", (gint) segment.size, | |
| segment.offset, get_marker_name (segment.marker)); | |
| if (segment.marker == GST_JPEG_MARKER_EOI) | |
| break; | |
| if (offset + segment.size < size && | |
| parse_jpeg_segment (&segment) && segment.size >= 0) | |
| offset = segment.offset + segment.size; | |
| else | |
| offset += 2; | |
| }; | |
| } | |
| static void | |
| process_file (const gchar * fn) | |
| { | |
| GError *err = NULL; | |
| gchar *data = NULL; | |
| gsize size = 0; | |
| guint i; | |
| g_print ("===============================================================\n"); | |
| g_print (" %s\n", fn); | |
| g_print ("===============================================================\n"); | |
| if (!g_file_get_contents (fn, &data, &size, &err)) { | |
| g_error ("%s", err->message); | |
| g_clear_error (&err); | |
| return; | |
| } | |
| parse_jpeg ((const guint8 *) data, size); | |
| for (i = 0; i < G_N_ELEMENTS (app_segments); ++i) { | |
| if (app_segments[i] != NULL) { | |
| GstMapInfo map = GST_MAP_INFO_INIT; | |
| /* Could parse/extract tags here */ | |
| gst_buffer_map (app_segments[i], &map, GST_MAP_READ); | |
| g_print ("\tAPP%-2u : %u bytes\n", i, (guint) map.size); | |
| gst_util_dump_mem ((guchar *) map.data, MIN (map.size, 16)); | |
| gst_buffer_unmap (app_segments[i], &map); | |
| gst_buffer_unref (app_segments[i]); | |
| app_segments[i] = NULL; | |
| } | |
| } | |
| g_free (data); | |
| } | |
| int | |
| main (int argc, gchar ** argv) | |
| { | |
| gchar **filenames = NULL; | |
| GOptionEntry options[] = { | |
| {G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &filenames, NULL}, | |
| {NULL} | |
| }; | |
| GOptionContext *ctx; | |
| GError *err = NULL; | |
| guint i, num; | |
| gst_init (&argc, &argv); | |
| if (argc == 1) { | |
| g_printerr ("Usage: %s FILE.JPG [FILE2.JPG] [FILE..JPG]\n", argv[0]); | |
| return -1; | |
| } | |
| ctx = g_option_context_new ("JPEG FILES"); | |
| g_option_context_add_main_entries (ctx, options, GETTEXT_PACKAGE); | |
| g_option_context_add_group (ctx, gst_init_get_option_group ()); | |
| if (!g_option_context_parse (ctx, &argc, &argv, &err)) { | |
| g_print ("Error initializing: %s\n", GST_STR_NULL (err->message)); | |
| g_option_context_free (ctx); | |
| g_clear_error (&err); | |
| exit (1); | |
| } | |
| g_option_context_free (ctx); | |
| if (filenames == NULL || *filenames == NULL) { | |
| g_printerr ("Please provide one or more filenames."); | |
| return 1; | |
| } | |
| num = g_strv_length (filenames); | |
| for (i = 0; i < num; ++i) { | |
| process_file (filenames[i]); | |
| } | |
| g_strfreev (filenames); | |
| return 0; | |
| } |