/* ------------------------------------------------------------------------ * Id Software's RoQ video file format decoder * * Dr. Tim Ferguson, 2001. * For more details on the algorithm: * http://www.csse.monash.edu.au/~timf/videocodec.html * * This is a simple decoder for the Id Software RoQ video format. In * this format, audio samples are DPCM coded and the video frames are * coded using motion blocks and vector quantisation. * * Note: All information on the RoQ file format has been obtained through * pure reverse engineering. This was achieved by giving known input * audio and video frames to the roq.exe encoder and analysing the * resulting output text and RoQ file. No decompiling of the Quake III * Arena game was required. * * You may freely use this source code. I only ask that you reference its * source in your projects documentation: * Tim Ferguson: http://www.csse.monash.edu.au/~timf/ * ------------------------------------------------------------------------ */ #include "quakedef.h" #if defined(HAVE_MEDIA_DECODER) && defined(Q3CLIENT) static int VFS_GETC(vfsfile_t *fp) { unsigned char c; VFS_READ(fp, &c, 1); return c; } //#include //#include //#include #include "roq.h" //#define DBUG 1 #define FAST /* -------------------------------------------------------------------------- */ static unsigned int get_word(vfsfile_t *fp) { unsigned int ret; ret = ((VFS_GETC(fp)) & 0xff); ret |= ((VFS_GETC(fp)) & 0xff) << 8; return(ret); } /* -------------------------------------------------------------------------- */ static unsigned long get_long(vfsfile_t *fp) { unsigned long ret; ret = ((VFS_GETC(fp)) & 0xff); ret |= ((VFS_GETC(fp)) & 0xff) << 8; ret |= ((VFS_GETC(fp)) & 0xff) << 16; ret |= ((VFS_GETC(fp)) & 0xff) << 24; return(ret); } /* -------------------------------------------------------------------------- */ static int roq_parse_file(vfsfile_t *fp, roq_info *ri) { unsigned int head1, head3, chunk_id;//, chunk_arg; long head2, chunk_size; qofs_t fpos; #ifndef FAST int max_frame; #endif #ifndef FAST ri->num_audio_bytes = ri->num_frames = max_frame = 0; ri->audio_channels = 0; ri->frame_offset = NULL; #endif ri->buf_size = 0; head1 = get_word(fp); head2 = get_long(fp); head3 = get_word(fp); if(head1 != 0x1084 && head2 != 0xffffffff && head3 != 0x1e) { return 1; } ri->roq_start = fpos = VFS_TELL(fp); while(fpos+8 <= ri->maxpos) { #if DBUG > 20 Con_Printf("---------------------------------------------------------------------------\n"); #endif VFS_SEEK(fp, fpos); chunk_id = get_word(fp); chunk_size = get_long(fp); /*chunk_arg =*/ get_word(fp); fpos += 8 + chunk_size; if (chunk_size == 0xffffffff || fpos > ri->maxpos) //FIXME: THIS SHOULD NOT HAPPEN break; if (chunk_size > ri->buf_size) ri->buf_size = chunk_size; #if DBUG > 20 Con_Printf("%03d 0x%06lx: chunk: 0x%02x size: %ld cells: 2x2=%d,4x4=%d\n", i, fpos, chunk_id, chunk_size, v1>>8,v1&0xff); #endif if(chunk_id == RoQ_INFO) /* video info */ { ri->width = get_word(fp); ri->height = get_word(fp); get_word(fp); get_word(fp); #ifdef FAST return 0; //we have all the data we need now. We always find a sound chunk first, or none at all. #endif } #ifndef FAST else if(chunk_id == RoQ_QUAD_VQ) { ri->num_frames++; if(ri->num_frames > max_frame) { max_frame += 5000; if((ri->frame_offset = BZ_Realloc(ri->frame_offset, sizeof(long) * max_frame)) == NULL) return 1; } ri->frame_offset[ri->num_frames] = fpos; } } #endif else if(chunk_id == RoQ_SOUND_MONO || chunk_id == RoQ_SOUND_STEREO) { if(chunk_id == RoQ_SOUND_MONO) ri->audio_channels = 1; else ri->audio_channels = 2; #ifndef FAST ri->num_audio_bytes += chunk_size; #endif } } return 0; } /* -------------------------------------------------------------------------- */ static void apply_vector_2x2(roq_info *ri, int x, int y, roq_cell *cell) { unsigned char *yptr; yptr = ri->y[0] + (y * ri->width) + x; *yptr++ = cell->y0; *yptr++ = cell->y1; yptr += (ri->width - 2); *yptr++ = cell->y2; *yptr++ = cell->y3; ri->u[0][(y/2) * (ri->width/2) + x/2] = cell->u; ri->v[0][(y/2) * (ri->width/2) + x/2] = cell->v; } /* -------------------------------------------------------------------------- */ static void apply_vector_4x4(roq_info *ri, int x, int y, roq_cell *cell) { unsigned long row_inc, c_row_inc; register unsigned char y0, y1, u, v; unsigned char *yptr, *uptr, *vptr; yptr = ri->y[0] + (y * ri->width) + x; uptr = ri->u[0] + (y/2) * (ri->width/2) + x/2; vptr = ri->v[0] + (y/2) * (ri->width/2) + x/2; row_inc = ri->width - 4; c_row_inc = (ri->width/2) - 2; *yptr++ = y0 = cell->y0; *uptr++ = u = cell->u; *vptr++ = v = cell->v; *yptr++ = y0; *yptr++ = y1 = cell->y1; *uptr++ = u; *vptr++ = v; *yptr++ = y1; yptr += row_inc; *yptr++ = y0; *yptr++ = y0; *yptr++ = y1; *yptr++ = y1; yptr += row_inc; uptr += c_row_inc; vptr += c_row_inc; *yptr++ = y0 = cell->y2; *uptr++ = u; *vptr++ = v; *yptr++ = y0; *yptr++ = y1 = cell->y3; *uptr++ = u; *vptr++ = v; *yptr++ = y1; yptr += row_inc; *yptr++ = y0; *yptr++ = y0; *yptr++ = y1; *yptr++ = y1; } /* -------------------------------------------------------------------------- */ static void apply_motion_4x4(roq_info *ri, int x, int y, unsigned char mv, char mean_x, char mean_y) { int i, mx, my; unsigned char *pa, *pb; mx = x + 8 - (mv >> 4) - mean_x; my = y + 8 - (mv & 0xf) - mean_y; pa = ri->y[0] + (y * ri->width) + x; pb = ri->y[1] + (my * ri->width) + mx; for(i = 0; i < 4; i++) { pa[0] = pb[0]; pa[1] = pb[1]; pa[2] = pb[2]; pa[3] = pb[3]; pa += ri->width; pb += ri->width; } pa = ri->u[0] + (y/2) * (ri->width/2) + x/2; pb = ri->u[1] + (my/2) * (ri->width/2) + (mx + 1)/2; for(i = 0; i < 2; i++) { pa[0] = pb[0]; pa[1] = pb[1]; pa += ri->width/2; pb += ri->width/2; } pa = ri->v[0] + (y/2) * (ri->width/2) + x/2; pb = ri->v[1] + (my/2) * (ri->width/2) + (mx + 1)/2; for(i = 0; i < 2; i++) { pa[0] = pb[0]; pa[1] = pb[1]; pa += ri->width/2; pb += ri->width/2; } } /* -------------------------------------------------------------------------- */ static void apply_motion_8x8(roq_info *ri, int x, int y, unsigned char mv, char mean_x, char mean_y) { int mx, my, i; unsigned char *pa, *pb; mx = x + 8 - (mv >> 4) - mean_x; my = y + 8 - (mv & 0xf) - mean_y; pa = ri->y[0] + (y * ri->width) + x; pb = ri->y[1] + (my * ri->width) + mx; for(i = 0; i < 8; i++) { pa[0] = pb[0]; pa[1] = pb[1]; pa[2] = pb[2]; pa[3] = pb[3]; pa[4] = pb[4]; pa[5] = pb[5]; pa[6] = pb[6]; pa[7] = pb[7]; pa += ri->width; pb += ri->width; } pa = ri->u[0] + (y/2) * (ri->width/2) + x/2; pb = ri->u[1] + (my/2) * (ri->width/2) + (mx + 1)/2; for(i = 0; i < 4; i++) { pa[0] = pb[0]; pa[1] = pb[1]; pa[2] = pb[2]; pa[3] = pb[3]; pa += ri->width/2; pb += ri->width/2; } pa = ri->v[0] + (y/2) * (ri->width/2) + x/2; pb = ri->v[1] + (my/2) * (ri->width/2) + (mx + 1)/2; for(i = 0; i < 4; i++) { pa[0] = pb[0]; pa[1] = pb[1]; pa[2] = pb[2]; pa[3] = pb[3]; pa += ri->width/2; pb += ri->width/2; } } /* -------------------------------------------------------------------------- */ roq_info *roq_open(char *fname) { vfsfile_t *fp; roq_info *ri; int i; if((fp = FS_OpenVFS(fname, "rb", FS_GAME)) == NULL) { if((fp = FS_OpenVFS(va("video/%s", fname), "rb", FS_GAME)) == NULL) //for q3 compat return NULL; } if((ri = BZF_Malloc(sizeof(roq_info))) == NULL) { Con_Printf("Error allocating memory.\n"); return NULL; } memset(ri, 0, sizeof(roq_info)); ri->maxpos = VFS_TELL(fp)+VFS_GETLEN(fp);//no adds/subracts for fileoffset here ri->fp = fp; if(roq_parse_file(fp, ri)) return NULL; #ifndef FAST ri->stream_length = (ri->num_frames * 1000)/30; #endif for(i = 0; i < 128; i++) { ri->snd_sqr_arr[i] = i * i; ri->snd_sqr_arr[i + 128] = -(i * i); } for(i = 0; i < 2; i++) { if((ri->y[i] = BZF_Malloc(ri->width * ri->height)) == NULL || (ri->u[i] = BZF_Malloc((ri->width * ri->height)/4)) == NULL || (ri->v[i] = BZF_Malloc((ri->width * ri->height)/4)) == NULL) { Con_Printf("Memory allocation error.\n"); return NULL; } } ri->buf_size *= 2; if((ri->buf = BZF_Malloc(ri->buf_size)) == NULL) { Con_Printf("Memory allocation error.\n"); return NULL; } ri->audio_buf_size = 0; ri->audio = NULL; ri->frame_num = 0; ri->aud_pos = ri->vid_pos = ri->roq_start; return ri; } //reset enough that we'll start decoding from the start next time we try to read a frame. void roq_rewind(roq_info *ri) { ri->frame_num = 0; ri->aud_pos = ri->vid_pos = ri->roq_start; } /* -------------------------------------------------------------------------- */ void roq_close(roq_info *ri) { int i; if(ri == NULL) return; VFS_CLOSE(ri->fp); for(i = 0; i < 2; i++) { if(ri->y[i] != NULL) BZ_Free(ri->y[i]); if(ri->u[i] != NULL) BZ_Free(ri->u[i]); if(ri->v[i] != NULL) BZ_Free(ri->v[i]); } if(ri->buf != NULL) BZ_Free(ri->buf); if (ri->audio) BZ_Free(ri->audio); BZ_Free(ri); } /* -------------------------------------------------------------------------- */ int roq_read_frame(roq_info *ri) { vfsfile_t *fp = ri->fp; unsigned int chunk_id = 0, chunk_arg = 0; unsigned long chunk_size = 0; int i, j, k, nv1, nv2, vqflg = 0, vqflg_pos = -1, vqid, bpos, xpos, ypos, xp, yp, x, y; unsigned char *tp, *buf; int frame_stats[2][4] = {{0},{0}}; roq_qcell *qcell; qofs_t fpos = ri->vid_pos; VFS_SEEK(fp, fpos); while(fpos+8 < ri->maxpos) { chunk_id = get_word(fp); chunk_size = get_long(fp); chunk_arg = get_word(fp); fpos += 8+chunk_size; if (chunk_size == 0xffffffff || fpos > ri->maxpos) return -1; if (chunk_id == RoQ_QUAD_VQ) break; if(chunk_id == RoQ_QUAD_CODEBOOK) { if((nv1 = chunk_arg >> 8) == 0) nv1 = 256; if((nv2 = chunk_arg & 0xff) == 0 && nv1 * 6 < chunk_size) nv2 = 256; VFS_READ(fp, ri->cells, nv1 * sizeof(roq_cell)); for(i = 0; i < nv2; i++) for(j = 0; j < 4; j++) ri->qcells[i].idx[j] = VFS_GETC(fp); } else VFS_SEEK(fp, fpos); } if(chunk_id != RoQ_QUAD_VQ) { ri->vid_pos = fpos; return 0; } ri->frame_num++; if(ri->buf_size < chunk_size) { ri->buf_size *= 2; if (ri->buf_size < chunk_size) //double wasn't enough ri->buf_size = chunk_size; BZ_Free(ri->buf); if((ri->buf = BZ_Malloc(ri->buf_size)) == NULL) { Con_Printf("Memory allocation error.\n"); return -1; } } VFS_READ(fp, ri->buf, chunk_size); buf = ri->buf; bpos = xpos = ypos = 0; while(bpos < chunk_size) { for(yp = ypos; yp < ypos + 16; yp += 8) for(xp = xpos; xp < xpos + 16; xp += 8) { if(vqflg_pos < 0) { vqflg = buf[bpos++]; vqflg |= (buf[bpos++] << 8); vqflg_pos = 7; } vqid = (vqflg >> (vqflg_pos * 2)) & 0x3; frame_stats[0][vqid]++; vqflg_pos--; switch(vqid) { case RoQ_ID_MOT: break; case RoQ_ID_FCC: apply_motion_8x8(ri, xp, yp, buf[bpos++], (char)(chunk_arg >> 8), (char)(chunk_arg & 0xff)); break; case RoQ_ID_SLD: qcell = ri->qcells + buf[bpos++]; apply_vector_4x4(ri, xp, yp, ri->cells + qcell->idx[0]); apply_vector_4x4(ri, xp+4, yp, ri->cells + qcell->idx[1]); apply_vector_4x4(ri, xp, yp+4, ri->cells + qcell->idx[2]); apply_vector_4x4(ri, xp+4, yp+4, ri->cells + qcell->idx[3]); break; case RoQ_ID_CCC: for(k = 0; k < 4; k++) { x = xp; y = yp; if(k & 0x01) x += 4; if(k & 0x02) y += 4; if(vqflg_pos < 0) { vqflg = buf[bpos++]; vqflg |= (buf[bpos++] << 8); vqflg_pos = 7; } vqid = (vqflg >> (vqflg_pos * 2)) & 0x3; frame_stats[1][vqid]++; vqflg_pos--; switch(vqid) { case RoQ_ID_MOT: break; case RoQ_ID_FCC: apply_motion_4x4(ri, x, y, buf[bpos++], (char)(chunk_arg >> 8), (char)(chunk_arg & 0xff)); break; case RoQ_ID_SLD: qcell = ri->qcells + buf[bpos++]; apply_vector_2x2(ri, x, y, ri->cells + qcell->idx[0]); apply_vector_2x2(ri, x+2, y, ri->cells + qcell->idx[1]); apply_vector_2x2(ri, x, y+2, ri->cells + qcell->idx[2]); apply_vector_2x2(ri, x+2, y+2, ri->cells + qcell->idx[3]); break; case RoQ_ID_CCC: apply_vector_2x2(ri, x, y, ri->cells + buf[bpos]); apply_vector_2x2(ri, x+2, y, ri->cells + buf[bpos+1]); apply_vector_2x2(ri, x, y+2, ri->cells + buf[bpos+2]); apply_vector_2x2(ri, x+2, y+2, ri->cells + buf[bpos+3]); bpos += 4; break; } } break; default: Con_Printf("Unknown vq code: %d\n", vqid); } } xpos += 16; if(xpos >= ri->width) { xpos -= ri->width; ypos += 16; } if(ypos >= ri->height) break; } #if 0 frame_stats[0][3] = 0; Con_Printf("<%d 0x%04x -> %d,%d>\n", ri->frame_num, chunk_arg, (char)(chunk_arg >> 8), (char)(chunk_arg & 0xff)); Con_Printf("for 08x08 CCC = %d, FCC = %d, MOT = %d, SLD = %d, PAT = 0\n", frame_stats[0][3], frame_stats[0][1], frame_stats[0][0], frame_stats[0][2]); Con_Printf("for 04x04 CCC = %d, FCC = %d, MOT = %d, SLD = %d, PAT = 0\n", frame_stats[1][3], frame_stats[1][1], frame_stats[1][0], frame_stats[1][2]); #endif ri->vid_pos = fpos; if(ri->frame_num == 1) { memcpy(ri->y[1], ri->y[0], ri->width * ri->height); memcpy(ri->u[1], ri->u[0], (ri->width * ri->height)/4); memcpy(ri->v[1], ri->v[0], (ri->width * ri->height)/4); } else { tp = ri->y[0]; ri->y[0] = ri->y[1]; ri->y[1] = tp; tp = ri->u[0]; ri->u[0] = ri->u[1]; ri->u[1] = tp; tp = ri->v[0]; ri->v[0] = ri->v[1]; ri->v[1] = tp; } return 1; } /* -------------------------------------------------------------------------- */ int roq_read_audio(roq_info *ri) { vfsfile_t *fp = ri->fp; unsigned int chunk_id = 0, chunk_arg = 0; unsigned long chunk_size = 0; int i, snd_left, snd_right; long fpos; fpos = ri->aud_pos; ri->audio_size = 0; for(;;) { VFS_SEEK(fp, fpos); if(fpos >= ri->maxpos) return -1; chunk_id = get_word(fp); chunk_size = get_long(fp); chunk_arg = get_word(fp); fpos += 8+chunk_size; if (chunk_size == 0xffffffff || fpos > ri->maxpos) return -1; if (chunk_id == RoQ_SOUND_MONO || chunk_id == RoQ_SOUND_STEREO) break; } if(ri->audio_buf_size < chunk_size*2) { if(ri->audio != NULL) BZ_Free(ri->audio); ri->audio=NULL; ri->audio_buf_size = chunk_size * 3; if (ri->audio_buf_size <= 0) return -1; if((ri->audio = BZ_Malloc(ri->audio_buf_size)) == NULL) return -1; } if (ri->audio_buf_size < 0) return -1; if(chunk_id == RoQ_SOUND_MONO) { ri->audio_size = chunk_size; snd_left = chunk_arg; for(i = 0; i < chunk_size; i++) { snd_left += (int)ri->snd_sqr_arr[(unsigned)VFS_GETC(fp)]; *(short *)&ri->audio[i * 2] = snd_left; } ri->aud_pos = fpos; return chunk_size; } if(chunk_id == RoQ_SOUND_STEREO) { ri->audio_size = chunk_size; snd_left = (chunk_arg & 0xFF00); snd_right = (chunk_arg & 0xFF) << 8; for(i = 0; i < chunk_size; i += 2) { snd_left += (int)ri->snd_sqr_arr[(unsigned)VFS_GETC(fp)]; snd_right += (int)ri->snd_sqr_arr[(unsigned)VFS_GETC(fp)]; *(short *)&ri->audio[i * 2] = snd_left & 0xffff; *(short *)&ri->audio[i * 2 + 2] = snd_right & 0xffff; } ri->aud_pos = fpos; return chunk_size; } ri->aud_pos = fpos; return 0; } #endif