| /* |
| * gstvp8parser.c - VP8 parser |
| * |
| * Copyright (C) 2013-2014 Intel Corporation |
| * Author: Halley Zhao <halley.zhao@intel.com> |
| * Author: Gwenole Beauchesne <gwenole.beauchesne@intel.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. |
| */ |
| |
| /** |
| * SECTION:gstvp8parser |
| * @short_description: Convenience library for parsing vp8 video bitstream. |
| * |
| * For more details about the structures, you can refer to the |
| * specifications: VP8-rfc6386.pdf |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| # include "config.h" |
| #endif |
| #include <string.h> |
| #include <gst/base/gstbytereader.h> |
| #include "gstvp8parser.h" |
| #include "gstvp8rangedecoder.h" |
| #include "vp8utils.h" |
| |
| GST_DEBUG_CATEGORY (vp8_parser_debug); |
| #define GST_CAT_DEFAULT vp8_parser_debug |
| |
| #define INITIALIZE_DEBUG_CATEGORY ensure_debug_category () |
| static void |
| ensure_debug_category (void) |
| { |
| #ifndef GST_DISABLE_GST_DEBUG |
| static gsize is_initialized; |
| |
| if (g_once_init_enter (&is_initialized)) { |
| GST_DEBUG_CATEGORY_INIT (vp8_parser_debug, "codecparsers_vp8", 0, |
| "vp8 parser library"); |
| g_once_init_leave (&is_initialized, TRUE); |
| } |
| #endif |
| } |
| |
| static GstVp8MvProbs vp8_mv_update_probs; |
| static GstVp8TokenProbs vp8_token_update_probs; |
| |
| static void |
| ensure_prob_tables (void) |
| { |
| static gsize is_initialized; |
| |
| if (g_once_init_enter (&is_initialized)) { |
| gst_vp8_mv_update_probs_init (&vp8_mv_update_probs); |
| gst_vp8_token_update_probs_init (&vp8_token_update_probs); |
| g_once_init_leave (&is_initialized, TRUE); |
| } |
| } |
| |
| #define READ_BOOL(rd, val, field_name) \ |
| val = vp8_read_bool ((rd)) |
| #define READ_UINT(rd, val, nbits, field_name) \ |
| val = vp8_read_uint ((rd), (nbits)) |
| #define READ_SINT(rd, val, nbits, field_name) \ |
| val = vp8_read_sint ((rd), (nbits)) |
| |
| static inline gboolean |
| vp8_read_bool (GstVp8RangeDecoder * rd) |
| { |
| return (gboolean) gst_vp8_range_decoder_read_literal (rd, 1); |
| } |
| |
| static inline guint |
| vp8_read_uint (GstVp8RangeDecoder * rd, guint nbits) |
| { |
| return (guint) gst_vp8_range_decoder_read_literal (rd, nbits); |
| } |
| |
| static inline gint |
| vp8_read_sint (GstVp8RangeDecoder * rd, guint nbits) |
| { |
| gint v; |
| |
| v = gst_vp8_range_decoder_read_literal (rd, nbits); |
| if (gst_vp8_range_decoder_read_literal (rd, 1)) |
| v = -v; |
| return v; |
| } |
| |
| /* Parse update_segmentation() */ |
| static gboolean |
| parse_update_segmentation (GstVp8RangeDecoder * rd, GstVp8Segmentation * seg) |
| { |
| gboolean update; |
| gint i; |
| |
| seg->update_mb_segmentation_map = FALSE; |
| seg->update_segment_feature_data = FALSE; |
| |
| READ_BOOL (rd, seg->segmentation_enabled, "segmentation_enabled"); |
| if (!seg->segmentation_enabled) |
| return TRUE; |
| |
| READ_BOOL (rd, seg->update_mb_segmentation_map, "update_mb_segmentation_map"); |
| READ_BOOL (rd, seg->update_segment_feature_data, |
| "update_segment_feature_data"); |
| |
| if (seg->update_segment_feature_data) { |
| READ_UINT (rd, seg->segment_feature_mode, 1, "segment_feature_mode"); |
| |
| /* quantizer_update_value defaults to zero if update flag is zero |
| (Section 9.3, 4.b) */ |
| for (i = 0; i < 4; i++) { |
| READ_BOOL (rd, update, "quantizer_update"); |
| if (update) { |
| READ_SINT (rd, seg->quantizer_update_value[i], 7, |
| "quantizer_update_value"); |
| } else |
| seg->quantizer_update_value[i] = 0; |
| } |
| |
| /* lf_update_value defaults to zero if update flag is zero |
| (Section 9.3, 4.b) */ |
| for (i = 0; i < 4; i++) { |
| READ_BOOL (rd, update, "loop_filter_update"); |
| if (update) { |
| READ_SINT (rd, seg->lf_update_value[i], 6, "lf_update_value"); |
| } else |
| seg->lf_update_value[i] = 0; |
| } |
| } |
| |
| /* segment_prob defaults to 255 if update flag is zero |
| (Section 9.3, 5) */ |
| if (seg->update_mb_segmentation_map) { |
| for (i = 0; i < 3; i++) { |
| READ_BOOL (rd, update, "segment_prob_update"); |
| if (update) { |
| READ_UINT (rd, seg->segment_prob[i], 8, "segment_prob"); |
| } else |
| seg->segment_prob[i] = 255; |
| } |
| } |
| return TRUE; |
| } |
| |
| /* Parse mb_lf_adjustments() to update loop filter delta adjustments */ |
| static gboolean |
| parse_mb_lf_adjustments (GstVp8RangeDecoder * rd, GstVp8MbLfAdjustments * adj) |
| { |
| gboolean update; |
| gint i; |
| |
| adj->mode_ref_lf_delta_update = FALSE; |
| |
| READ_BOOL (rd, adj->loop_filter_adj_enable, "loop_filter_adj_enable"); |
| if (!adj->loop_filter_adj_enable) |
| return TRUE; |
| |
| READ_BOOL (rd, adj->mode_ref_lf_delta_update, "mode_ref_lf_delta_update"); |
| if (!adj->mode_ref_lf_delta_update) |
| return TRUE; |
| |
| for (i = 0; i < 4; i++) { |
| READ_BOOL (rd, update, "ref_frame_delta_update_flag"); |
| if (update) { |
| READ_SINT (rd, adj->ref_frame_delta[i], 6, "ref_frame_delta_magniture"); |
| } |
| } |
| |
| for (i = 0; i < 4; i++) { |
| READ_BOOL (rd, update, "mb_mode_delta_update_flag"); |
| if (update) { |
| READ_SINT (rd, adj->mb_mode_delta[i], 6, "mb_mode_delta_magnitude"); |
| } |
| } |
| return TRUE; |
| } |
| |
| /* Parse quant_indices() */ |
| static gboolean |
| parse_quant_indices (GstVp8RangeDecoder * rd, GstVp8QuantIndices * qip) |
| { |
| gboolean update; |
| |
| READ_UINT (rd, qip->y_ac_qi, 7, "y_ac_qi"); |
| |
| READ_BOOL (rd, update, "y_dc_delta_present"); |
| if (update) { |
| READ_SINT (rd, qip->y_dc_delta, 4, "y_dc_delta_magnitude"); |
| } else |
| qip->y_dc_delta = 0; |
| |
| READ_BOOL (rd, update, "y2_dc_delta_present"); |
| if (update) { |
| READ_SINT (rd, qip->y2_dc_delta, 4, "y2_dc_delta_magnitude"); |
| } else |
| qip->y2_dc_delta = 0; |
| |
| READ_BOOL (rd, update, "y2_ac_delta_present"); |
| if (update) { |
| READ_SINT (rd, qip->y2_ac_delta, 4, "y2_ac_delta_magnitude"); |
| } else |
| qip->y2_ac_delta = 0; |
| |
| READ_BOOL (rd, update, "uv_dc_delta_present"); |
| if (update) { |
| READ_SINT (rd, qip->uv_dc_delta, 4, "uv_dc_delta_magnitude"); |
| } else |
| qip->uv_dc_delta = 0; |
| |
| READ_BOOL (rd, update, "uv_ac_delta_present"); |
| if (update) { |
| READ_SINT (rd, qip->uv_ac_delta, 4, "uv_ac_delta_magnitude"); |
| } else |
| qip->uv_ac_delta = 0; |
| |
| return TRUE; |
| } |
| |
| /* Parse token_prob_update() to update persistent token probabilities */ |
| static gboolean |
| parse_token_prob_update (GstVp8RangeDecoder * rd, GstVp8TokenProbs * probs) |
| { |
| gint i, j, k, l; |
| guint8 prob; |
| |
| for (i = 0; i < 4; i++) { |
| for (j = 0; j < 8; j++) { |
| for (k = 0; k < 3; k++) { |
| for (l = 0; l < 11; l++) { |
| if (gst_vp8_range_decoder_read (rd, |
| vp8_token_update_probs.prob[i][j][k][l])) { |
| READ_UINT (rd, prob, 8, "token_prob_update"); |
| probs->prob[i][j][k][l] = prob; |
| } |
| } |
| } |
| } |
| } |
| return TRUE; |
| } |
| |
| /* Parse prob_update() to update probabilities used for MV decoding */ |
| static gboolean |
| parse_mv_prob_update (GstVp8RangeDecoder * rd, GstVp8MvProbs * probs) |
| { |
| gint i, j; |
| guint8 prob; |
| |
| for (i = 0; i < 2; i++) { |
| for (j = 0; j < 19; j++) { |
| if (gst_vp8_range_decoder_read (rd, vp8_mv_update_probs.prob[i][j])) { |
| READ_UINT (rd, prob, 7, "mv_prob_update"); |
| probs->prob[i][j] = prob ? (prob << 1) : 1; |
| } |
| } |
| } |
| return TRUE; |
| } |
| |
| /* Calculate partition sizes */ |
| static gboolean |
| calc_partition_sizes (GstVp8FrameHdr * frame_hdr, const guint8 * data, |
| guint size) |
| { |
| const guint num_partitions = 1 << frame_hdr->log2_nbr_of_dct_partitions; |
| guint i, ofs, part_size, part_size_ofs = frame_hdr->first_part_size; |
| |
| ofs = part_size_ofs + 3 * (num_partitions - 1); |
| if (ofs > size) { |
| GST_ERROR ("not enough bytes left to parse partition sizes"); |
| return FALSE; |
| } |
| |
| /* The size of the last partition is not specified (9.5) */ |
| for (i = 0; i < num_partitions - 1; i++) { |
| part_size = (guint32) data[part_size_ofs + 0] | |
| ((guint32) data[part_size_ofs + 1] << 8) | |
| ((guint32) data[part_size_ofs + 2] << 16); |
| part_size_ofs += 3; |
| |
| frame_hdr->partition_size[i] = part_size; |
| ofs += part_size; |
| } |
| |
| if (ofs > size) { |
| GST_ERROR ("not enough bytes left to determine the last partition size"); |
| return FALSE; |
| } |
| frame_hdr->partition_size[i] = size - ofs; |
| |
| while (++i < G_N_ELEMENTS (frame_hdr->partition_size)) |
| frame_hdr->partition_size[i] = 0; |
| return TRUE; |
| } |
| |
| /* Parse uncompressed data chunk (19.1) */ |
| static GstVp8ParserResult |
| parse_uncompressed_data_chunk (GstVp8Parser * parser, GstByteReader * br, |
| GstVp8FrameHdr * frame_hdr) |
| { |
| guint32 frame_tag, start_code; |
| guint16 size_code; |
| |
| GST_DEBUG ("parsing \"Uncompressed Data Chunk\""); |
| |
| if (!gst_byte_reader_get_uint24_le (br, &frame_tag)) |
| goto error; |
| |
| frame_hdr->key_frame = !(frame_tag & 0x01); |
| frame_hdr->version = (frame_tag >> 1) & 0x07; |
| frame_hdr->show_frame = (frame_tag >> 4) & 0x01; |
| frame_hdr->first_part_size = (frame_tag >> 5) & 0x7ffff; |
| |
| if (frame_hdr->key_frame) { |
| if (!gst_byte_reader_get_uint24_be (br, &start_code)) |
| goto error; |
| if (start_code != 0x9d012a) |
| GST_WARNING ("vp8 parser: invalid start code in frame header"); |
| |
| if (!gst_byte_reader_get_uint16_le (br, &size_code)) |
| goto error; |
| frame_hdr->width = size_code & 0x3fff; |
| frame_hdr->horiz_scale_code = size_code >> 14; |
| |
| if (!gst_byte_reader_get_uint16_le (br, &size_code)) { |
| goto error; |
| } |
| frame_hdr->height = size_code & 0x3fff; |
| frame_hdr->vert_scale_code = (size_code >> 14); |
| |
| /* Reset parser state on key frames */ |
| gst_vp8_parser_init (parser); |
| } else { |
| frame_hdr->width = 0; |
| frame_hdr->height = 0; |
| frame_hdr->horiz_scale_code = 0; |
| frame_hdr->vert_scale_code = 0; |
| } |
| |
| /* Calculated values */ |
| frame_hdr->data_chunk_size = gst_byte_reader_get_pos (br); |
| return GST_VP8_PARSER_OK; |
| |
| error: |
| GST_WARNING ("error parsing \"Uncompressed Data Chunk\""); |
| return GST_VP8_PARSER_ERROR; |
| } |
| |
| /* Parse Frame Header (19.2) */ |
| static GstVp8ParserResult |
| parse_frame_header (GstVp8Parser * parser, GstVp8RangeDecoder * rd, |
| GstVp8FrameHdr * frame_hdr) |
| { |
| gboolean update; |
| guint i; |
| |
| GST_DEBUG ("parsing \"Frame Header\""); |
| |
| if (frame_hdr->key_frame) { |
| READ_UINT (rd, frame_hdr->color_space, 1, "color_space"); |
| READ_UINT (rd, frame_hdr->clamping_type, 1, "clamping_type"); |
| } |
| |
| if (!parse_update_segmentation (rd, &parser->segmentation)) |
| goto error; |
| |
| READ_UINT (rd, frame_hdr->filter_type, 1, "filter_type"); |
| READ_UINT (rd, frame_hdr->loop_filter_level, 6, "loop_filter_level"); |
| READ_UINT (rd, frame_hdr->sharpness_level, 3, "sharpness_level"); |
| |
| if (!parse_mb_lf_adjustments (rd, &parser->mb_lf_adjust)) |
| goto error; |
| |
| READ_UINT (rd, frame_hdr->log2_nbr_of_dct_partitions, 2, |
| "log2_nbr_of_dct_partitions"); |
| |
| if (!parse_quant_indices (rd, &frame_hdr->quant_indices)) |
| goto error; |
| |
| frame_hdr->copy_buffer_to_golden = 0; |
| frame_hdr->copy_buffer_to_alternate = 0; |
| if (frame_hdr->key_frame) { |
| READ_BOOL (rd, frame_hdr->refresh_entropy_probs, "refresh_entropy_probs"); |
| |
| frame_hdr->refresh_last = TRUE; |
| frame_hdr->refresh_golden_frame = TRUE; |
| frame_hdr->refresh_alternate_frame = TRUE; |
| |
| gst_vp8_mode_probs_init_defaults (&frame_hdr->mode_probs, TRUE); |
| } else { |
| READ_BOOL (rd, frame_hdr->refresh_golden_frame, "refresh_golden_frame"); |
| READ_BOOL (rd, frame_hdr->refresh_alternate_frame, |
| "refresh_alternate_frame"); |
| |
| if (!frame_hdr->refresh_golden_frame) { |
| READ_UINT (rd, frame_hdr->copy_buffer_to_golden, 2, |
| "copy_buffer_to_golden"); |
| } |
| |
| if (!frame_hdr->refresh_alternate_frame) { |
| READ_UINT (rd, frame_hdr->copy_buffer_to_alternate, 2, |
| "copy_buffer_to_alternate"); |
| } |
| |
| READ_UINT (rd, frame_hdr->sign_bias_golden, 1, "sign_bias_golden"); |
| READ_UINT (rd, frame_hdr->sign_bias_alternate, 1, "sign_bias_alternate"); |
| READ_BOOL (rd, frame_hdr->refresh_entropy_probs, "refresh_entropy_probs"); |
| READ_BOOL (rd, frame_hdr->refresh_last, "refresh_last"); |
| |
| memcpy (&frame_hdr->mode_probs, &parser->mode_probs, |
| sizeof (parser->mode_probs)); |
| } |
| memcpy (&frame_hdr->token_probs, &parser->token_probs, |
| sizeof (parser->token_probs)); |
| memcpy (&frame_hdr->mv_probs, &parser->mv_probs, sizeof (parser->mv_probs)); |
| |
| if (!parse_token_prob_update (rd, &frame_hdr->token_probs)) |
| goto error; |
| |
| READ_BOOL (rd, frame_hdr->mb_no_skip_coeff, "mb_no_skip_coeff"); |
| if (frame_hdr->mb_no_skip_coeff) { |
| READ_UINT (rd, frame_hdr->prob_skip_false, 8, "prob_skip_false"); |
| } |
| |
| if (!frame_hdr->key_frame) { |
| READ_UINT (rd, frame_hdr->prob_intra, 8, "prob_intra"); |
| READ_UINT (rd, frame_hdr->prob_last, 8, "prob_last"); |
| READ_UINT (rd, frame_hdr->prob_gf, 8, "prob_gf"); |
| |
| READ_BOOL (rd, update, "intra_16x16_prob_update_flag"); |
| if (update) { |
| for (i = 0; i < 4; i++) { |
| READ_UINT (rd, frame_hdr->mode_probs.y_prob[i], 8, "intra_16x16_prob"); |
| } |
| } |
| |
| READ_BOOL (rd, update, "intra_chroma_prob_update_flag"); |
| if (update) { |
| for (i = 0; i < 3; i++) { |
| READ_UINT (rd, frame_hdr->mode_probs.uv_prob[i], 8, |
| "intra_chroma_prob"); |
| } |
| } |
| |
| if (!parse_mv_prob_update (rd, &frame_hdr->mv_probs)) |
| goto error; |
| } |
| |
| /* Refresh entropy probabilities */ |
| if (frame_hdr->refresh_entropy_probs) { |
| memcpy (&parser->token_probs, &frame_hdr->token_probs, |
| sizeof (frame_hdr->token_probs)); |
| memcpy (&parser->mv_probs, &frame_hdr->mv_probs, |
| sizeof (frame_hdr->mv_probs)); |
| if (!frame_hdr->key_frame) |
| memcpy (&parser->mode_probs, &frame_hdr->mode_probs, |
| sizeof (frame_hdr->mode_probs)); |
| } |
| |
| /* Calculated values */ |
| frame_hdr->header_size = gst_vp8_range_decoder_get_pos (rd); |
| return GST_VP8_PARSER_OK; |
| |
| error: |
| GST_WARNING ("error parsing \"Frame Header\""); |
| return GST_VP8_PARSER_ERROR; |
| } |
| |
| /**** API ****/ |
| /** |
| * gst_vp8_parser_init: |
| * @parser: The #GstVp8Parser to initialize |
| * |
| * Initializes the supplied @parser structure with its default values. |
| * |
| * Since: 1.4 |
| */ |
| void |
| gst_vp8_parser_init (GstVp8Parser * parser) |
| { |
| g_return_if_fail (parser != NULL); |
| |
| memset (&parser->segmentation, 0, sizeof (parser->segmentation)); |
| memset (&parser->mb_lf_adjust, 0, sizeof (parser->mb_lf_adjust)); |
| gst_vp8_token_probs_init_defaults (&parser->token_probs); |
| gst_vp8_mv_probs_init_defaults (&parser->mv_probs); |
| gst_vp8_mode_probs_init_defaults (&parser->mode_probs, FALSE); |
| } |
| |
| /** |
| * gst_vp8_parser_parse_frame_header: |
| * @parser: The #GstVp8Parser |
| * @frame_hdr: The #GstVp8FrameHdr to fill |
| * @data: The data to parse |
| * @size: The size of the @data to parse |
| * |
| * Parses the VP8 bitstream contained in @data, and fills in @frame_hdr |
| * with the information. The supplied @data shall point to a complete |
| * frame since there is no sync code specified for VP8 bitstreams. Thus, |
| * the @size argument shall represent the whole frame size. |
| * |
| * Returns: a #GstVp8ParserResult |
| * |
| * Since: 1.4 |
| */ |
| GstVp8ParserResult |
| gst_vp8_parser_parse_frame_header (GstVp8Parser * parser, |
| GstVp8FrameHdr * frame_hdr, const guint8 * data, gsize size) |
| { |
| GstByteReader br; |
| GstVp8RangeDecoder rd; |
| GstVp8RangeDecoderState rd_state; |
| GstVp8ParserResult result; |
| |
| ensure_debug_category (); |
| ensure_prob_tables (); |
| |
| g_return_val_if_fail (frame_hdr != NULL, GST_VP8_PARSER_ERROR); |
| g_return_val_if_fail (parser != NULL, GST_VP8_PARSER_ERROR); |
| |
| /* Uncompressed Data Chunk */ |
| gst_byte_reader_init (&br, data, size); |
| |
| result = parse_uncompressed_data_chunk (parser, &br, frame_hdr); |
| if (result != GST_VP8_PARSER_OK) |
| return result; |
| |
| /* Frame Header */ |
| if (frame_hdr->data_chunk_size + frame_hdr->first_part_size > size) |
| return GST_VP8_PARSER_BROKEN_DATA; |
| |
| data += frame_hdr->data_chunk_size; |
| size -= frame_hdr->data_chunk_size; |
| if (!gst_vp8_range_decoder_init (&rd, data, frame_hdr->first_part_size)) |
| return GST_VP8_PARSER_BROKEN_DATA; |
| |
| result = parse_frame_header (parser, &rd, frame_hdr); |
| if (result != GST_VP8_PARSER_OK) |
| return result; |
| |
| /* Calculate partition sizes */ |
| if (!calc_partition_sizes (frame_hdr, data, size)) |
| return GST_VP8_PARSER_BROKEN_DATA; |
| |
| /* Sync range decoder state */ |
| gst_vp8_range_decoder_get_state (&rd, &rd_state); |
| frame_hdr->rd_range = rd_state.range; |
| frame_hdr->rd_value = rd_state.value; |
| frame_hdr->rd_count = rd_state.count; |
| return GST_VP8_PARSER_OK; |
| } |