blob: 6e0b66b172d3a033e039b36e2a9ee0196ab89ad6 [file] [log] [blame]
#ifdef ANDROID
//#ifndef NULL
//#define NULL (void*)0x0
//#endif
#define true 1
#define false 0
#endif
#include "viddec_pm.h"
#include "viddec_fw_debug.h"
#include "viddec_fw_common_defs.h"
#include "viddec_pm_tags.h"
#include "viddec_parser_ops.h"
#include "viddec_vc1_parse.h"
#include "viddec_mp4_parse.h"
#include "viddec_mpeg2_parse.h"
#include "viddec_h264_parse.h"
/*
Overview of Parser manager:
Parser manager is the glue between Kernel(main.c) and actual codecs. We abstract common functionality as much as we can
in this module. The parser Manager context allocates memory for Parsers. At any point in time there is only one active stream.
During open stream we setup all necessary initialisation for the codec we are handling. The parser manager context is
stored on DDR when the current stream gets swapped out by the kernel. When the next stream comes in it has it's own
version of parser manager.
Parser manager is reponsible for providing information on when its a good time to swap a stream.
High level algorithm of parser Manager once a stream is opened and active(RET's are returns to Kernel):
1. create a list data structure to hold any incoming ES descriptors.
2. Check to see if any of the ES buffers Desc in current list has data to be processed. If not request kernel(RET) for a buffer.
3. If data is present parse until a scprefix+sc is found. If not goto step2.
4. If startcode detected update list state to make ES data look like Linear buffer.
5. Setup required state to provide getbits interface for codecs to access bit stream maximum 32bits at a time.
6. Setup Current & Next workloads provided by Kernel.
7. Call the codec to parse the data we collected between start codes.
8. Query to see if we parsed frame worth of data.
9. Do necessary TAG association and remove used buffers from List.
10. Send information to kernel on whether workload is done or Not.(RET). When kernel reschedules start from step2.
Kernel can swap current stream at RET points described above.
Other additional things supported:
- Generic start code detect function which is same for most of codecs.
- Memory Management.
- Flush of stream.
- Emulation prevention.
- Interface to emit necessary tags for codec specific types.
*/
/* check to see if codec needs emulation prevention */
#define EMUL_REQD(codec) ((codec == MFD_STREAM_FORMAT_VC1) || (codec_type == MFD_STREAM_FORMAT_H264) ? 1: 0)
#ifdef RTL_SIMULATION
extern void output_omar_wires( unsigned int value );
#else
#define output_omar_wires(x)
#endif
/* Place to store Function pointers for all supported interfaces for each codec */
viddec_parser_ops_t parser_ops[MFD_STREAM_FORMAT_MAX];
/* we need to define as external function so that for host mode we can use the same code without
modifications by overloading dma function with a copy function
*/
extern uint32_t cp_using_dma(uint32_t ddr_addr, uint32_t local_addr, uint32_t size, char to_ddr, char swap);
void viddec_pm_init_ops()
{
viddec_vc1_get_ops(&parser_ops[MFD_STREAM_FORMAT_VC1]);
parser_ops[MFD_STREAM_FORMAT_VC1].parse_sc = viddec_parse_sc;
parser_ops[MFD_STREAM_FORMAT_VC1].gen_contrib_tags = viddec_pm_generic_generate_contribution_tags;
parser_ops[MFD_STREAM_FORMAT_VC1].gen_assoc_tags = viddec_generic_add_association_tags;
viddec_mpeg2_get_ops(&parser_ops[MFD_STREAM_FORMAT_MPEG]);
parser_ops[MFD_STREAM_FORMAT_MPEG].parse_sc = viddec_parse_sc;
parser_ops[MFD_STREAM_FORMAT_MPEG].gen_contrib_tags = viddec_pm_generic_generate_contribution_tags;
parser_ops[MFD_STREAM_FORMAT_MPEG].gen_assoc_tags = viddec_mpeg2_add_association_tags;
viddec_h264_get_ops(&parser_ops[MFD_STREAM_FORMAT_H264]);
parser_ops[MFD_STREAM_FORMAT_H264].parse_sc = viddec_parse_sc;
parser_ops[MFD_STREAM_FORMAT_H264].gen_contrib_tags = viddec_pm_lateframe_generate_contribution_tags;
parser_ops[MFD_STREAM_FORMAT_H264].gen_assoc_tags = viddec_h264_add_association_tags;
viddec_mp4_get_ops(&parser_ops[MFD_STREAM_FORMAT_MPEG42]);
parser_ops[MFD_STREAM_FORMAT_MPEG42].gen_contrib_tags = viddec_pm_generic_generate_contribution_tags;
parser_ops[MFD_STREAM_FORMAT_MPEG42].gen_assoc_tags = viddec_generic_add_association_tags;
}
/*
Returns size of persistent DDR memory required for the codec. If the required memory is less than max allocated
scratch memory in FW we always give the max scratch size.
*/
uint32_t viddec_pm_get_parser_sizes(uint32_t codec_type, viddec_parser_memory_sizes_t *size)
{
parser_ops[codec_type].get_cxt_size(size);
if(size->context_size > MAX_CODEC_CXT_SIZE)
{
DEB("ERROR: size(%d) of context for codec=%d is greater than max=%d\n",size->context_size,codec_type,MAX_CODEC_CXT_SIZE);
}
size->context_size = sizeof(viddec_pm_cxt_t);
return 1;
}
/*
Initialize the scratch memory allocated to the stream based on clean. if clean is true initialize to
start state, if not then preserve stream information.
*/
void viddec_pm_init_context(viddec_pm_cxt_t *cxt, uint32_t codec_type, uint32_t *persist_mem, uint32_t clean)
{
int i;
for(i=0; i<MAX_IBUFS_PER_SC; i++)
{
cxt->pending_tags.pending_tags[i] = INVALID_ENTRY;
}
cxt->frame_start_found = false;
cxt->found_fm_st_in_current_au = false;
cxt->late_frame_detect = (MFD_STREAM_FORMAT_H264 == codec_type) ? true:false;
cxt->pending_tags.first_buf_aligned = cxt->pending_tags.using_next = cxt->pending_tags.frame_done =false;
cxt->next_workload_error_eos = VIDDEC_FW_WORKLOAD_ERR_FLUSHED_FRAME | VIDDEC_FW_WORKLOAD_ERR_NOTDECODABLE;
viddec_pm_utils_list_init(&(cxt->list));
cxt->cur_buf.list_index = -1;
cxt->parse_cubby.phase=0;
parser_ops[codec_type].init((void *)&(cxt->codec_data[0]), persist_mem, !clean);
if(clean)
{
cxt->pending_inband_tags = 0;
}
else
{
/* TODO: Enable this once codecs support this function */
//parser_ops[codec_type].flush_preserve((void *)&(cxt->codec_data[0]), persist_mem);
}
}
void viddec_pm_update_time(viddec_pm_cxt_t *cxt, uint32_t time)
{
viddec_emit_time(&(cxt->emitter), time);
}
/* add an esbuffer to list */
static inline uint32_t viddec_pm_add_es_buf_to_list(viddec_pm_cxt_t *cxt, viddec_input_buffer_t *es_buf)
{
uint32_t val , ret = PM_OVERFLOW;
val = viddec_pm_utils_list_addbuf(&(cxt->list), es_buf);
if(val == 1) ret = PM_SUCCESS;
return ret;
}
static inline uint32_t viddec_pm_check_inband_messages(viddec_pm_sc_cur_buf_t *cur_buf, uint32_t *type)
{
uint32_t ret=false;
if(cur_buf->cur_es->flags != 0)
{
/* update offset to point to next position for loading data */
cur_buf->cur_offset +=(cur_buf->cur_size);
cur_buf->cur_size = 0;
switch(cur_buf->cur_es->flags)
{
case VIDDEC_STREAM_EOS:
{
*type = PM_EOS;
}
break;
case VIDDEC_STREAM_DISCONTINUITY:
{
*type = PM_DISCONTINUITY;
}
default:
break;
}
ret =true;
}
return ret;
}
/* creates an ibuf from the current position in list. Fills sc_parse_cubby_cxt */
uint32_t viddec_pm_create_ibuf(viddec_pm_cxt_t *cxt)
{
uint32_t ret = PM_NO_DATA;
#ifndef VBP
viddec_sc_parse_cubby_cxt_t *cubby = &(cxt->parse_cubby);
#endif
viddec_pm_sc_cur_buf_t *cur_buf = &(cxt->cur_buf);
viddec_pm_utils_list_t *list = &(cxt->list);
/* Step1: check if list is Empty, If yes return No data */
if(list->num_items > 0)
{
/* Step 2: Check to see If current index into list is empty & we have data in list,
if so increment index and initialise it*/
if(cur_buf->list_index == -1)
{
if(viddec_pm_utils_list_getbyte_position(list,
list->first_scprfx_length+1,
(uint32_t *)&(cur_buf->list_index),
&(cur_buf->cur_offset)) != 1)
{/* This return's offset and index from where we have to start for sc detect */
cur_buf->cur_size = 0;
cur_buf->cur_es = &(list->sc_ibuf[cur_buf->list_index]);
}
else
{
return PM_NO_DATA;
}
}
/* Step3: If we are done with current buffer then try to go to next item in list */
if((cur_buf->cur_offset + cur_buf->cur_size) >= cur_buf->cur_es->len)
{
/* Need to handle In band messages before going to next buffer */
//if(viddec_pm_check_inband_messages(cur_buf))
if(viddec_pm_check_inband_messages(cur_buf, &ret))
{
return ret;
}
/* If no items in list after the current buffer return no data */
if((uint32_t)(cur_buf->list_index + 1) >= list->num_items)
{
return PM_NO_DATA;
}
cur_buf->list_index++;
cur_buf->cur_es = &(list->sc_ibuf[cur_buf->list_index]);
cur_buf->cur_offset = cur_buf->cur_size = 0;
}
/* Step4: Fill the cubby with data to send to parser sc code function */
{
int32_t data_left;
/* data left is the leftout size in current ES buffer */
data_left = cur_buf->cur_es->len - (cur_buf->cur_offset + cur_buf->cur_size);
/* update offset to point to next position for loading data */
cur_buf->cur_offset +=(cur_buf->cur_size);
#ifndef VBP
/* Load maximum of array size */
if(data_left >= SC_DETECT_BUF_SIZE)
{
data_left = SC_DETECT_BUF_SIZE;
}
/* can be zero if we have zero sized buffers in our list.EX:NEW segment */
if(data_left > 0)
{/* do a copy using Linear Dma */
uint32_t size , ddr_addr = 0, ddr_mask=0;
/* get ddr adress of current offset in ES buffer */
#ifdef HOST_ONLY
ddr_addr = cur_buf->cur_offset + (uint32_t)cur_buf->cur_es->buf;
#else
ddr_addr = cur_buf->cur_offset + cur_buf->cur_es->phys;
#endif
ddr_mask = (ddr_addr & 3);
ddr_addr = ddr_addr & ~3;
/* return from this function can be more bytes based on input buf alignment.
The adress for local memory we are sending is on DWORD boundary so it should be safe.
*/
size = cp_using_dma(ddr_addr, (uint32_t)&(cxt->scbuf[0]), data_left+ddr_mask, 0,1);//false, true);
cubby->size = data_left;
/* point to actual memory location which has the data(skip aligment bytes) */
cubby->buf = &(cxt->scbuf[ddr_mask]);
cur_buf->cur_size = data_left;
ret = PM_SUCCESS;
}
else
{
/* If we completely consumed this buffer or this is a zero sized buffer we want to check inband messages */
//if(viddec_pm_check_inband_messages(cur_buf))
if(viddec_pm_check_inband_messages(cur_buf, &ret))
{
return ret;
}
}
#else
ret = PM_SUCCESS;
#endif
}
}
return ret;
}
/*
Read data from esbuffer list and parse for start codes or EOS. If we consumed all the data we return no data left.
*/
static inline uint32_t viddec_pm_parse_for_sccode(viddec_pm_cxt_t *cxt, viddec_parser_ops_t *func)
{
uint32_t ret = PM_NO_DATA;
uint32_t sc_boundary_found = 0;
while(!sc_boundary_found)
{
/* Create an buffer from list to parse */
ret = viddec_pm_create_ibuf(cxt);
switch(ret)
{
case PM_NO_DATA:
{/* No data in esbuffer list for parsing sc */
sc_boundary_found = 1;
}
break;
case PM_EOS:
case PM_DISCONTINUITY:
{
sc_boundary_found = 1;
cxt->list.end_offset = cxt->cur_buf.cur_offset+1;
cxt->parse_cubby.phase = 0;
/* we didn't find a start code so second start code length would be 0 */
cxt->sc_prefix_info.second_scprfx_length = 0;
//cxt->sc_prefix_info.next_sc = VIDDEC_PARSE_EOS;
if(ret == PM_EOS)
{
cxt->sc_prefix_info.next_sc = VIDDEC_PARSE_EOS;
}
if(ret == PM_DISCONTINUITY)
{
cxt->sc_prefix_info.next_sc = VIDDEC_PARSE_DISCONTINUITY;
}
}
break;
case PM_SUCCESS:
default:
{
/* parse the created buffer for sc */
ret = func->parse_sc((void *)&(cxt->parse_cubby), (void *)&(cxt->codec_data[0]), &(cxt->sc_prefix_info));
if(ret == 1)
{
cxt->list.end_offset = cxt->parse_cubby.sc_end_pos + cxt->cur_buf.cur_offset;
cxt->parse_cubby.phase = 0;
cxt->list.total_bytes+=cxt->parse_cubby.sc_end_pos;
ret = PM_SC_FOUND;
sc_boundary_found = 1;
break;
}
else
{
cxt->list.total_bytes+=cxt->cur_buf.cur_size;
}
}
break;
}
}
return ret;
}
/*
Once we are ready to flush the current workload, we update current workload on DDR with our internal information
that was not written before like num of items in workload, errors in stream etc...
*/
void viddec_pm_finalize_workload(viddec_pm_cxt_t *cxt, uint32_t codec_type, uint32_t codec_errors)
{
viddec_emit_set_codec(&(cxt->emitter), codec_type);
viddec_emit_set_codec_errors(&(cxt->emitter), codec_errors);
viddec_emit_flush_current_wkld(&(cxt->emitter));
output_omar_wires( 0x5 );
output_omar_wires( 0x1 );
}
/*
After parsing between start codes we cleanup our list so that it has only buffers that are not consumed yet.
*/
uint32_t viddec_pm_finalize_list(viddec_pm_cxt_t *cxt)
{
uint32_t ret=1;
viddec_pm_utils_list_remove_used_entries(&(cxt->list), cxt->sc_prefix_info.second_scprfx_length);
cxt->cur_buf.list_index = -1;
cxt->list.first_scprfx_length = cxt->sc_prefix_info.second_scprfx_length;
return ret;
}
/* Case to handle if we encounter list overflow without seeing second start code */
void viddec_pm_handle_buffer_overflow(viddec_pm_cxt_t *cxt, uint32_t codec_type, viddec_input_buffer_t *es_buf)
{
uint32_t indx=0;
while(indx< (uint32_t)cxt->list.num_items)
{/* Dump tags for all entries in list to prevent buffer leak */
viddec_emit_contr_tag(&(cxt->emitter), &(cxt->list.sc_ibuf[indx]), false, true);
viddec_emit_assoc_tag(&(cxt->emitter), cxt->list.sc_ibuf[indx].id, true);
indx++;
}
/* Dump tags for the new buffer that was received */
viddec_emit_contr_tag(&(cxt->emitter), es_buf, 0, true);
viddec_emit_assoc_tag(&(cxt->emitter), es_buf->id, true);
/* Set errors on both current and next as both can be invalid */
viddec_emit_set_workload_error(&(cxt->emitter),
(VIDDEC_FW_WORKLOAD_ERR_BUFFERS_OVERFLOW | VIDDEC_FW_WORKLOAD_ERR_NOTDECODABLE),
true);
viddec_emit_set_workload_error(&(cxt->emitter),
(VIDDEC_FW_WORKLOAD_ERR_BUFFERS_OVERFLOW | VIDDEC_FW_WORKLOAD_ERR_NOTDECODABLE),
false);
/* cleanup the pending tags */
viddec_pm_generate_missed_association_tags(cxt, true);
viddec_pm_finalize_workload(cxt, codec_type, 0);
WRITE_SVEN(SVEN_MODULE_EVENT_GV_FW_FATAL_BUFFER_OVERLFOW, (int)es_buf->phys, (int)es_buf->len, 0, 0, 0, 0);
}
static inline void viddec_pm_handle_post_inband_messages(viddec_pm_cxt_t *cxt, uint32_t m_type)
{
if((m_type & ~(0xFF))== PM_INBAND_MESSAGES)
{
/* If EOS decide set error on next workload too */
viddec_emit_set_workload_error(&(cxt->emitter), cxt->next_workload_error_eos, true);
if(m_type == PM_EOS)
{
viddec_emit_set_inband_tag(&(cxt->emitter), VIDDEC_WORKLOAD_IBUF_EOS, true);
}
if(m_type == PM_DISCONTINUITY)
{
cxt->pending_inband_tags = PM_DISCONTINUITY;
}
}
}
static inline uint32_t viddec_pm_handle_new_es_buffer(viddec_pm_cxt_t *cxt, uint32_t codec_type, viddec_input_buffer_t *es_buf)
{
uint32_t state = PM_SUCCESS;
if(es_buf != NULL)
{
state = viddec_pm_add_es_buf_to_list(cxt, es_buf);
if(state == PM_OVERFLOW)
{
viddec_pm_handle_buffer_overflow(cxt, codec_type, es_buf);
}
}
return state;
}
static inline void viddec_pm_handle_pre_inband_messages(viddec_pm_cxt_t *cxt)
{
if(cxt->pending_inband_tags == PM_DISCONTINUITY)
{
viddec_emit_set_inband_tag(&(cxt->emitter), VIDDEC_WORKLOAD_IBUF_DISCONTINUITY, false);
cxt->pending_inband_tags = 0;
}
}
/*
Main function of parser manager.
It searches until start codes are found int he list if not through return type indicates kernel to provide more buffers.
If a start code is found it calls the codec to parse the syntax data it accumulated so far.
If codec says a frame is not done then continues to find the next start code.
If codec says frame is done it does tag association and indicates kernel a frame is done.
*/
uint32_t viddec_pm_parse_es_buffer(viddec_pm_cxt_t *cxt, uint32_t codec_type, viddec_input_buffer_t *es_buf)
{
uint32_t state = PM_SUCCESS;
/* Step1: Append Es buffer to list */
viddec_pm_handle_pre_inband_messages(cxt);
state = viddec_pm_handle_new_es_buffer(cxt, codec_type, es_buf);
if(state == PM_SUCCESS)
{
uint32_t scdetect_ret;
output_omar_wires( 0x3 );
/* Step2: Phase1 of parsing, parse until a sc is found */
scdetect_ret = viddec_pm_parse_for_sccode(cxt,&parser_ops[codec_type]);
switch(scdetect_ret)
{
case PM_NO_DATA:
{
/* Step3: If we consumed all the data indicate we need more buffers */
state = PM_NO_DATA;
break;
}
case PM_EOS:
case PM_DISCONTINUITY:
case PM_SC_FOUND:
{
uint32_t codec_errors=0;
/* Create necessary state information to make the ES buffers look like linear data */
viddec_pm_utils_list_updatebytepos(&(cxt->list), cxt->sc_prefix_info.second_scprfx_length);
if(cxt->sc_prefix_info.first_sc_detect != 1)
{
/* Step4: If we saw two start codes init state and call codec to parse */
uint32_t codec_ret;
/* Initialise the state to provide get bits for codecs */
viddec_pm_utils_bstream_init(&(cxt->getbits), &(cxt->list), EMUL_REQD(codec_type));
output_omar_wires( 0x1 );
/* call the codec to do synatax parsing */
parser_ops[codec_type].parse_syntax((void *)cxt, (void *)&(cxt->codec_data[0]));
/* Check and see if frame start was detected. If we did update frame start in current au */
if(parser_ops[codec_type].is_frame_start((void *)&(cxt->codec_data[0])) == true)
{
cxt->frame_start_found += 1;
cxt->found_fm_st_in_current_au = true;
}
/* Query to see if we reached end of current frame */
codec_ret = parser_ops[codec_type].is_wkld_done((void *)cxt,
(void *)&(cxt->codec_data[0]),
(uint32_t)(cxt->sc_prefix_info.next_sc),
&codec_errors);
state = (codec_ret == VIDDEC_PARSE_FRMDONE) ? PM_WKLD_DONE : PM_SUCCESS;
/* generate contribution and association tags */
cxt->pending_tags.frame_done = (codec_ret == VIDDEC_PARSE_FRMDONE);
parser_ops[codec_type].gen_assoc_tags(cxt);
parser_ops[codec_type].gen_contrib_tags(cxt, (state != PM_WKLD_DONE));
}
else
{
/* Step4: If this is the first start code in this stream, clean up and return */
if(cxt->list.total_bytes != 0)
{
viddec_pm_generic_generate_contribution_tags(cxt, true);
viddec_generic_add_association_tags(cxt);
}
else
{
if(cxt->list.num_items >= 1)
{
uint32_t indx=0;
while((indx< (uint32_t)cxt->list.num_items) && (cxt->list.sc_ibuf[indx].len == 0))
{/* Dump all zero sized buffers until we see a buffer with valid data */
viddec_emit_contr_tag(&(cxt->emitter), &(cxt->list.sc_ibuf[indx]), false, false);
viddec_emit_assoc_tag(&(cxt->emitter), cxt->list.sc_ibuf[indx].id, false);
indx++;
}
}
}
if((scdetect_ret & ~(0xFF))!= PM_INBAND_MESSAGES)
{
state = PM_SUCCESS;//state = PM_FIRST_SC_FOUND;
cxt->sc_prefix_info.first_sc_detect = 0;
}
else
{
state = PM_WKLD_DONE;
}
}
viddec_pm_handle_post_inband_messages(cxt, scdetect_ret);
/* Step 5: If current frame is done, finalise the workload state with necessary information */
if(state == PM_WKLD_DONE)
{
DEB("\nFRAME ... DONE\n");
/* we decrement frame start. This can be 0 in cases like sending junk data with EOS */
cxt->frame_start_found -= (cxt->frame_start_found)? 1: 0;
if((scdetect_ret & ~(0xFF))== PM_INBAND_MESSAGES)
{/* If EOS dump pending tags and set state */
viddec_pm_generate_missed_association_tags(cxt, false);
state = scdetect_ret;
}
/* Write back stored state of workloads to memory to prepare for psuhing to output queue */
viddec_pm_finalize_workload(cxt, codec_type, codec_errors);
}
/* Step 6: Reset the list to prepare for next iteration */
viddec_pm_finalize_list(cxt);
break;
}
default:
break;
}
}//if(state == PM_SUCCESS)
return state;
} // viddec_pm_parse_es_buffer