This commit is contained in:
libretroadmin committed 2026-09-21 18:59:01 +02:00
1 parent dc7894e765
commit 455694476c
32 files changed
+788 -104

No files matched your search

+2
View File
@@ -20,6 +20,8 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include <retro_posix_source.h>
#include <math.h>
#include <stdlib.h>
+2
View File
@@ -20,6 +20,8 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include <retro_posix_source.h>
#include <stdint.h>
#include <stdlib.h>
#include <stddef.h>
+2
View File
@@ -20,6 +20,8 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include <retro_posix_source.h>
#include <stdlib.h>
#include <string.h>
+2
View File
@@ -20,6 +20,8 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include <retro_posix_source.h>
#include <stdlib.h>
#include <string.h>
+2
View File
@@ -20,6 +20,8 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include <retro_posix_source.h>
#include <stdlib.h>
#include <string.h>
+2
View File
@@ -20,6 +20,8 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include <retro_posix_source.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
+2
View File
@@ -20,6 +20,8 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include <retro_posix_source.h>
#include <stdlib.h>
#include <file/file_path.h>
+2
View File
@@ -20,6 +20,8 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include <retro_posix_source.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+2
View File
@@ -20,6 +20,8 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include <retro_posix_source.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+2 -3
View File
@@ -20,13 +20,12 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include <retro_posix_source.h>
#include <file/nbio.h>
#if defined(__linux__)
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
+2
View File
@@ -1,3 +1,5 @@
#include <retro_posix_source.h>
#include <formats/cdfs.h>
#include <retro_miscellaneous.h>
+544 -97
View File
@@ -30,6 +30,11 @@
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <retro_atomic.h>
#ifdef HAVE_THREADS
#include <rthreads/rthreads.h>
#include <rthreads/tpool.h>
#endif
#include <compat/intrinsics.h>
@@ -1597,8 +1602,20 @@ typedef struct {
/* The counted block behind a frame's planes (defined with the block
* allocator below); its row counter is what a reference read waits on. */
struct rh264_block_hdr;
struct rh264_pic_ctx;
static struct rh264_block_hdr *rh264_block_of(const void *data);
static int rh264_block_rows_final(const void *data);
#ifdef HAVE_THREADS
static void rh264_ctx_join(struct rh264_pic_ctx *c);
/* One lock and condition for every row publication and every wait on
* one, shared by the decoders in the process: a publication is a
* release store and a broadcast, a wait sleeps until the count it
* needs is there. Made by the first decoder given a pool; the
* thumbnail streams decode on one worker thread, which is what makes
* that first call safe. */
static slock_t *rh264_rows_lock;
static scond_t *rh264_rows_cond;
#endif
/* store one raw sample (I_PCM) at sample index 'i' of a plane pointer */
static RH264_INLINE void rh264_pcm_put(const rh264_frame *f, uint8_t *p,
@@ -3035,33 +3052,55 @@ static void rh264_inter_clear_i4mode(rh264_frame *f, int mbx, int mby)
* single thread the whole picture is final before it is a reference
* and this returns at once; a picture on another thread waits here,
* and nowhere else. */
static int rh264_ref_wait_misses;
static retro_atomic_int_t rh264_ref_wait_misses;
int rh264_video_ref_wait_misses(void)
{
return rh264_ref_wait_misses;
return retro_atomic_load_acquire_int(&rh264_ref_wait_misses);
}
static void rh264_ref_wait_rows(const rh264_frame *ref, int oy, int bh, int mvy)
static void rh264_ref_wait_rows(const rh264_frame *f, const rh264_frame *ref,
int oy, int bh, int mvy)
{
int y_last, rows;
if (!ref || !ref->planes)
return;
/* The second field of a pair predicts from the first, in the same
* block, which is counted as a whole when the pair completes: the
* pair decodes on one thread, in order, and needs no wait on
* itself. */
if (f && f->planes == ref->planes)
return;
y_last = oy + bh + (mvy >> 2) + 3;
if (y_last < 0)
return;
/* A field view interleaves the frame's lines: its line n is the
* frame's line 2n or 2n+1, and the row counter counts the frame's
* macroblock rows. */
if (ref->field)
y_last = y_last * 2 + 1;
rows = y_last / 16 + 1;
if (rows > ref->mbh)
rows = ref->mbh;
if (rh264_block_rows_final(ref->planes) < rows)
{
/* Single-threaded this is unreachable: a reference is complete
* before anything reads it. The threaded decoder will wait here
* on the block's row counter. Counted so a sample can assert
* that it never happens on one thread - if it did, the counter
* or its readers would be wrong, and a threaded decoder would
* wait for a row that had already been read. */
rh264_ref_wait_misses++;
#ifdef HAVE_THREADS
/* Another thread is still decoding the reference: sleep until
* it has published the row. Counted as well, for the sample
* that runs one thread and asserts this never happens there -
* a short read on one thread would be a wrong counter, and
* a wait here for a row already handed over. */
if (rh264_rows_lock)
{
/* a wait, not a miss: on the pool the rows arrive */
slock_lock(rh264_rows_lock);
while (rh264_block_rows_final(ref->planes) < rows)
scond_wait(rh264_rows_cond, rh264_rows_lock);
slock_unlock(rh264_rows_lock);
return;
}
#endif
retro_atomic_fetch_add_int(&rh264_ref_wait_misses, 1);
}
}
@@ -3077,7 +3116,7 @@ static void rh264_inter_pred_block(rh264_frame *f, const rh264_frame *ref,
* row for real reference data. */
int rw = ref->mbw * 16, rh = ref->mbh * 16;
uint8_t *dY = f->Y + RH264_OFF(f, oy * f->ystride + ox);
rh264_ref_wait_rows(ref, oy, bh, mvy);
rh264_ref_wait_rows(f, ref, oy, bh, mvy);
rh264_mc_luma(f, dY, f->ystride, ref->Y, ref->ystride, rw, rh,
ox, oy, bw, bh, mvx, mvy);
if (f->c444)
@@ -3658,6 +3697,9 @@ static void rh264_b_mc_tmp(const rh264_frame *f, uint8_t *ty, uint8_t *tu,
{
int rw = ref->mbw * 16, rh = ref->mbh * 16;
int cmvy = mvy;
/* The B predictions read their references here, not through
* rh264_inter_pred_block: the same wait, on the same rows. */
rh264_ref_wait_rows(f, ref, oy, bh, mvy);
if (!c422 && curfield && ref->field && curfield != ref->field)
cmvy += (curfield == 1) ? -2 : 2;
rh264_mc_luma(f, ty, 16, ref->Y, ref->ystride, rw, rh, ox, oy, bw, bh,
@@ -4143,6 +4185,13 @@ static int rh264_decode_bslice(rh264_bits *b, const rh264_sps *sps,
int mbx, mby;
int mb_type;
rh264_mb_pos(mbaddr, f->mbw, f->mbaff, &mbx, &mby);
/* temporal direct reads RefPicList1[0]'s co-located cells of this
* row, from a picture that may still be decoding: its motion is
* final with the row's samples, so the same wait, for this row
* and the pair row under it, once per macroblock (it returns at
* once when the rows are there) */
if (bc->n1 > 0 && bc->l1[0])
rh264_ref_wait_rows(f, bc->l1[0], mby * 16, 32, 0);
/* no further run or macroblock once the slice's RBSP is exhausted */
if (skip_run <= 0 && !rh264_more_rbsp(b)) break;
@@ -4879,6 +4928,28 @@ typedef struct {
#define RH264_MAX_SLICES 64
/* One slice's data decode, queued on its picture's context: the RBSP
* (its own copy - the context's unescape scratch is reused per NAL),
* the bit position the slice data starts at, the slice header, and
* the reference lists as the submitter built them, pointing at the
* context's snapshots. The queue is what a pool thread drains; drained
* on the submitting thread it is the decoder as it was. */
typedef struct rh264_slice_job
{
struct rh264_slice_job *next;
uint8_t *rbsp;
size_t rbsp_len;
size_t bitpos;
rh264_slice_hdr sh;
int kind; /* 0 intra, 1 P, 2 B */
int cabac;
int nref;
const rh264_frame *l0[34];
signed char picid[34];
int l0poc[34];
rh264_bctx bc; /* B only */
} rh264_slice_job;
/* Everything that belongs to the picture being decoded, as distinct
* from the sequence: the working frame, its motion grids, the slice
* bookkeeping, the slice scratch and the NAL unescape buffer. One of
@@ -4890,6 +4961,11 @@ typedef struct rh264_pic_ctx
{
uint8_t *rbsp_scratch;
size_t rbsp_scratch_cap;
/* The parameter sets this picture decodes with, copied when it
* opens: a later SPS or PPS in the stream must not change them
* under a picture still decoding. */
rh264_sps sps;
rh264_pps pps;
/* The pictures this one predicts from, as copies of their frames
* holding their own references on the sample and motion blocks.
* A slice decoder reads its lists through these rather than through
@@ -4924,8 +5000,18 @@ typedef struct rh264_pic_ctx
int lpn0[34], lpn1[34];
int lpn[34], l0poc[34];
signed char picid[34];
const rh264_frame *l0[34]; /* a P slice's list, once built */
rh264_cabac cb;
} sscr;
/* The queued slices of this picture, in order, and how the run of
* them ended: 0 while they are still fine, the decoder's -1 once one
* refused. */
rh264_slice_job *jobs, *jobs_tail;
int job_rc;
int completed; /* rh264_ctx_complete_picture has run */
int posted; /* the queue went to the pool */
retro_atomic_int_t busy; /* a pool thread is still on this picture */
const struct rh264_vlc_set *vlc;
} rh264_pic_ctx;
#define RH264_MAX_CTX 4
@@ -4939,6 +5025,11 @@ struct rh264_video
rh264_pic_ctx ctx[RH264_MAX_CTX];
rh264_pic_ctx *cur; /* the picture being decoded */
int nctx; /* contexts in rotation, 1..RH264_MAX_CTX */
#ifdef HAVE_THREADS
void *pool; /* tpool_t the pictures decode on, or NULL */
#endif
int threaded; /* pictures decode on the pool, in flight */
int broken; /* a posted picture's decode refused */
/* unescaped-RBSP scratch for slice NALs, grown on demand and kept
* for the decoder's lifetime */
rh264_sps sps;
@@ -5046,7 +5137,9 @@ struct rh264_video
* decoder its own. */
typedef struct rh264_block_hdr
{
int refs;
/* Holders let go from pool threads and the sequence's thread alike:
* the count is atomic, and the last one out frees or recycles. */
retro_atomic_int_t refs;
size_t len;
struct rh264_block_hdr *next_free;
/* How many macroblock rows of the picture in this block are final:
@@ -5056,7 +5149,7 @@ typedef struct rh264_block_hdr
* decoding from this one as a reference reads up to a row and no
* further, so this is the one number it has to wait on; on a single
* thread it is complete before anyone asks. */
int rows_final;
retro_atomic_int_t rows_final;
} rh264_block_hdr;
static rh264_block_hdr *rh264_block_of(const void *data)
@@ -5066,7 +5159,46 @@ static rh264_block_hdr *rh264_block_of(const void *data)
static int rh264_block_rows_final(const void *data)
{
return rh264_block_of(data)->rows_final;
return retro_atomic_load_acquire_int(&rh264_block_of(data)->rows_final);
}
#ifdef HAVE_THREADS
/* A test knob: hold each row's publication for a random number of
* thread yields before making it, so that a picture reading from this one finds its rows
* missing far more often than real content ever arranges, and the
* wait is what decodes the picture rather than luck. Output must be
* byte-exact under it, or the wait is wrong. 0 (the default) is off.
* Debug only: rh264_video_set_publish_delay() sets it. */
static int rh264_publish_delay_us; /* yields, at most, per row */
static retro_atomic_int_t rh264_publish_seed; /* every thread draws from it */
#endif
/* Publish that @rows rows of the picture in @data are final. */
static void rh264_block_publish_rows(void *data, int rows)
{
rh264_block_hdr *b = rh264_block_of(data);
#ifdef HAVE_THREADS
if (rh264_publish_delay_us > 0 && rows > 0)
{
/* a small linear congruential draw, and that many yields: a
* test knob, so a spin that gives the core away is fine here */
unsigned r = (unsigned)retro_atomic_fetch_add_int(&rh264_publish_seed, 0x9E3779B1);
unsigned n;
r = r * 1103515245u + 12345u;
n = (r >> 16) % (unsigned)rh264_publish_delay_us;
while (n--)
sthread_yield();
}
if (rh264_rows_lock)
{
slock_lock(rh264_rows_lock);
retro_atomic_store_release_int(&b->rows_final, rows);
scond_broadcast(rh264_rows_cond);
slock_unlock(rh264_rows_lock);
return;
}
#endif
retro_atomic_store_release_int(&b->rows_final, rows);
}
#define RH264_FREE_BLOCKS 8
@@ -5074,25 +5206,49 @@ static int rh264_block_rows_final(const void *data)
static rh264_block_hdr *rh264_free_blocks;
static int rh264_free_blocks_n;
#ifdef HAVE_THREADS
static slock_t *rh264_blocks_lock; /* the free list, once there is a pool */
#endif
static void rh264_blocks_lock_take(void)
{
#ifdef HAVE_THREADS
if (rh264_blocks_lock)
slock_lock(rh264_blocks_lock);
#endif
}
static void rh264_blocks_lock_drop(void)
{
#ifdef HAVE_THREADS
if (rh264_blocks_lock)
slock_unlock(rh264_blocks_lock);
#endif
}
static uint8_t *rh264_block_new(size_t len)
{
rh264_block_hdr **pp = &rh264_free_blocks, *b;
rh264_block_hdr **pp, *b;
rh264_blocks_lock_take();
pp = &rh264_free_blocks;
while ((b = *pp))
{
if (b->len == len)
{
*pp = b->next_free;
rh264_free_blocks_n--;
b->refs = 1;
b->rows_final = 0;
rh264_blocks_lock_drop();
retro_atomic_store_release_int(&b->refs, 1);
retro_atomic_store_release_int(&b->rows_final, 0);
return (uint8_t*)b + RH264_PLANES_HDR;
}
pp = &b->next_free;
}
rh264_blocks_lock_drop();
b = (rh264_block_hdr*)calloc(len + RH264_PLANES_HDR, 1);
if (!b)
return NULL;
b->refs = 1;
retro_atomic_store_release_int(&b->refs, 1);
b->len = len;
return (uint8_t*)b + RH264_PLANES_HDR;
}
@@ -5103,15 +5259,18 @@ static void rh264_block_release(void *data)
if (!data)
return;
b = (rh264_block_hdr*)((uint8_t*)data - RH264_PLANES_HDR);
if (--b->refs != 0)
if (retro_atomic_fetch_sub_int(&b->refs, 1) != 1)
return;
rh264_blocks_lock_take();
if (rh264_free_blocks_n < RH264_FREE_BLOCKS)
{
b->next_free = rh264_free_blocks;
rh264_free_blocks = b;
rh264_free_blocks_n++;
rh264_blocks_lock_drop();
return;
}
rh264_blocks_lock_drop();
free(b);
}
@@ -5127,7 +5286,7 @@ static void rh264_planes_release(uint8_t *planes)
static int rh264_planes_refs(const uint8_t *planes)
{
return planes ? *(const int*)(planes - RH264_PLANES_HDR) : 0;
return planes ? retro_atomic_load_acquire_int(&rh264_block_of(planes)->refs) : 0;
}
/* @dst takes @src's plane block, letting go of whatever it held. The
@@ -5139,7 +5298,7 @@ static void rh264_frame_share_planes(rh264_frame *dst, const rh264_frame *src)
return;
rh264_planes_release(dst->planes);
dst->planes = src->planes;
++*(int*)(dst->planes - RH264_PLANES_HDR);
retro_atomic_fetch_add_int(&rh264_block_of(dst->planes)->refs, 1);
dst->Yb = src->Yb; dst->Ub = src->Ub; dst->Vb = src->Vb;
dst->Y = dst->Yb; dst->U = dst->Ub; dst->V = dst->Vb;
}
@@ -5184,7 +5343,7 @@ static void rh264_mvg_release(rh264_mv *g)
static int rh264_mvg_refs(const rh264_mv *g)
{
return g ? *(const int*)((const uint8_t*)g - RH264_PLANES_HDR) : 0;
return g ? retro_atomic_load_acquire_int(&rh264_block_of(g)->refs) : 0;
}
/* The slot takes the working grid; whatever counted block it held
@@ -5193,7 +5352,7 @@ static void rh264_frame_share_mvg(rh264_frame *dst, rh264_mv *g)
{
if (dst->mvg_shared)
rh264_mvg_release(dst->mvg);
++*(int*)((uint8_t*)g - RH264_PLANES_HDR);
retro_atomic_fetch_add_int(&rh264_block_of(g)->refs, 1);
dst->mvg = g;
dst->mvg2 = g;
dst->mvg_shared = 1;
@@ -5234,9 +5393,9 @@ static const rh264_frame *rh264_ctx_snapshot(rh264_pic_ctx *c,
d = &c->refs[c->nrefs++];
*d = *src;
if (d->planes)
++rh264_block_of(d->planes)->refs;
retro_atomic_fetch_add_int(&rh264_block_of(d->planes)->refs, 1);
if (d->mvg_shared && d->mvg)
++rh264_block_of(d->mvg)->refs;
retro_atomic_fetch_add_int(&rh264_block_of(d->mvg)->refs, 1);
/* the snapshot owns nothing carved out of the slot's meta */
d->meta = NULL;
d->mvg_meta = NULL; d->mvg2_meta = NULL;
@@ -5553,6 +5712,16 @@ void rh264_video_close(rh264_video *v)
int i;
for (i = 0; i < RH264_MAX_CTX; i++)
{
#ifdef HAVE_THREADS
rh264_ctx_join(&v->ctx[i]);
#endif
while (v->ctx[i].jobs)
{
rh264_slice_job *j = v->ctx[i].jobs;
v->ctx[i].jobs = j->next;
free(j->rbsp);
free(j);
}
rh264_ctx_release_refs(&v->ctx[i]);
rh264_frame_free(&v->ctx[i].f);
free(v->ctx[i].rbsp_scratch);
@@ -8330,6 +8499,13 @@ static int rh264_cabac_decode_bslice(rh264_bits *b, const rh264_sps *sps,
uint8_t *upskip, *uptype;
int leftskip, lefttype;
rh264_mb_pos(mba, mbw, f->mbaff, &mbx, &mby);
/* temporal direct reads RefPicList1[0]'s co-located cells of this
* row, from a picture that may still be decoding: its motion is
* final with the row's samples, so the same wait, for this row
* and the pair row under it, once per macroblock (it returns at
* once when the rows are there) */
if (bc->n1 > 0 && bc->l1[0])
rh264_ref_wait_rows(f, bc->l1[0], mby * 16, 32, 0);
bot = f->mbaff && (mba & 1);
if (mbx == 0 && !bot)
{
@@ -8974,9 +9150,6 @@ static int rh264_out_push(rh264_video *v, int poc, int is_idr)
* the copy - their two pictures fill the working planes
* incrementally, which a swapped-in stale buffer would break. */
rh264_frame_share_planes(&v->out[slot], &v->cur->f);
v->out[slot].qp = v->cur->f.qp;
v->out[slot].chroma_qp_offset = v->cur->f.chroma_qp_offset;
v->out[slot].chroma_qp_offset2 = v->cur->f.chroma_qp_offset2;
}
else
rh264_frame_copy_planes(&v->out[slot], &v->cur->f);
@@ -8996,6 +9169,22 @@ static int rh264_out_push(rh264_video *v, int poc, int is_idr)
|| (v->out_gen[i] == v->out_gen[bi] && v->out_poc[i] < v->out_poc[bi]))
bi = i;
}
#ifdef HAVE_THREADS
/* Pictures decoding on the pool: the one due leaves only once it
* is complete. Held otherwise, and the next push asks again - the
* reorder depth is what gives the pool its head start. When the
* queue is full it must leave, and the wait is on its last row. */
if (v->threaded && v->out[bi].planes && v->out[bi].mbh > 0
&& rh264_block_rows_final(v->out[bi].planes) < v->out[bi].mbh)
{
if (v->out_len < RH264_OUT_SLOTS - 1)
return -1;
slock_lock(rh264_rows_lock);
while (rh264_block_rows_final(v->out[bi].planes) < v->out[bi].mbh)
scond_wait(rh264_rows_cond, rh264_rows_lock);
slock_unlock(rh264_rows_lock);
}
#endif
v->out_used[bi] = 0; v->out_len--;
v->out_show = bi;
return bi;
@@ -9036,39 +9225,61 @@ static int rh264_video_note_slice(rh264_video *v, const rh264_slice_hdr *sh)
* cells of other slices read as undecoded, exactly the neighbour
* availability 6.4.8 asks for; the accumulated copy is what deblocking,
* reference storage and temporal direct prediction see. */
static void rh264_video_fold_mvg(rh264_video *v, int first, int end)
static void rh264_ctx_fold_mvg(rh264_pic_ctx *c, int first, int end)
{
int gw = v->cur->f.mbw * 4, mb;
if (!v->cur->mvg || !v->cur->pic_mvg) return;
int gw = c->f.mbw * 4, mb;
if (!c->mvg || !c->pic_mvg) return;
for (mb = first; mb < end; mb++)
{
int gx = (mb % v->cur->f.mbw) * 4, gy = (mb / v->cur->f.mbw) * 4, r;
int gx = (mb % c->f.mbw) * 4, gy = (mb / c->f.mbw) * 4, r;
for (r = 0; r < 4; r++)
memcpy(v->cur->pic_mvg + (gy + r) * gw + gx,
v->cur->mvg + (gy + r) * gw + gx, 4 * sizeof(rh264_mv));
memcpy(c->pic_mvg + (gy + r) * gw + gx,
c->mvg + (gy + r) * gw + gx, 4 * sizeof(rh264_mv));
}
}
static void rh264_video_row_done(void *user, int mby);
static void rh264_video_post_picture(rh264_video *v);
static void rh264_video_finish_picture(rh264_video *v, int *got_pic);
#ifdef HAVE_THREADS
static void rh264_ctx_join(rh264_pic_ctx *c);
#endif
/* A new picture takes the next context round and readies it for the
* sequence's geometry. The one it leaves keeps its frame: that is the
* last picture's, shared into the DPB and the output queue, and the
* context will take a block of its own when it comes round again. */
static int rh264_video_next_ctx(rh264_video *v)
static int rh264_video_next_ctx(rh264_video *v, int *got_pic)
{
if (v->threaded)
{
/* The picture leaving decodes on the pool from here; its place in
* the sequence is settled now, with its rows still counting up. */
rh264_video_post_picture(v);
rh264_video_finish_picture(v, got_pic);
}
if (v->nctx > 1)
{
int i = (int)(v->cur - v->ctx);
v->cur = &v->ctx[(i + 1) % v->nctx];
}
#ifdef HAVE_THREADS
rh264_ctx_join(v->cur);
#endif
v->cur->posted = 0;
return rh264_ctx_prepare(v, v->cur);
}
static int rh264_video_decode_idr(rh264_video *v, const uint8_t *nal, size_t len)
static rh264_slice_job *rh264_ctx_queue_slice(rh264_pic_ctx *c,
const rh264_bits *b, const rh264_slice_hdr *sh, int kind, int cabac);
static void rh264_ctx_run_slices(rh264_pic_ctx *c, const struct rh264_vlc_set *vlc);
static int rh264_video_decode_idr(rh264_video *v, const uint8_t *nal, size_t len, int *got_pic)
{
int nut, nri, rc, end = 0;
int nut, nri, rc;
size_t rl;
uint8_t *rbsp;
rh264_bits b;
@@ -9089,8 +9300,12 @@ static int rh264_video_decode_idr(rh264_video *v, const uint8_t *nal, size_t len
int fld = sh.field_pic_flag ? (sh.bottom_field_flag ? 2 : 1) : 0;
int second = fld && v->pair_open && v->pair_frame_num == sh.frame_num
&& v->cur_field && v->cur_field != fld;
/* The picture leaving is sequenced by the rotation, which needs
* it still open; it closes once the new one has its context. */
if (!second && rh264_video_next_ctx(v, got_pic) != 0) return -1;
v->cur->pic_open = 0;
if (!second && rh264_video_next_ctx(v) != 0) return -1;
v->cur->sps = v->sps;
v->cur->pps = v->pps;
if (!second) v->idr_gen++;
v->last_poc = rh264_derive_poc(v, &sh, nri);
v->cur_field = fld;
@@ -9105,10 +9320,12 @@ static int rh264_video_decode_idr(rh264_video *v, const uint8_t *nal, size_t len
v->cur->pic_open = 1; v->cur->pic_kind = 1; v->cur->pic_ref = nri;
v->cur->pic_end = 0; v->cur->pic_nslices = 0;
v->cur->f.rows_deblocked = 0;
v->cur->f.row_user = v;
v->cur->f.row_user = v->cur;
v->cur->f.row_done = (v->cur->f.mbaff || v->cur->f.field) ? NULL : rh264_video_row_done;
if (v->cur->f.planes)
rh264_block_of(v->cur->f.planes)->rows_final = 0;
rh264_block_publish_rows(v->cur->f.planes, 0);
v->cur->job_rc = 0;
v->cur->completed = 0;
v->pend_idr_ltr = sh.idr_ltr;
rh264_resolve_scaling(&v->sps, &v->pps, v->cur->f.w4, v->cur->f.w8);
}
@@ -9119,14 +9336,13 @@ static int rh264_video_decode_idr(rh264_video *v, const uint8_t *nal, size_t len
{ v->cur->pic_open = 0; return -1; }
/* Main/High-profile streams use CABAC entropy coding; baseline uses CAVLC.
* Dispatch on the PPS entropy_coding_mode_flag. Both paths are intra-only. */
if (v->pps.entropy_coding_mode_flag)
rc = rh264_cabac_decode_islice(&b, &v->sps, &v->pps, &sh, &v->cur->f, &end,
&v->cur->sscr.cb);
else
rc = rh264_decode_islice(&b, &v->sps, &v->pps, &sh, &v->cur->f, &end);
if (rc == 0)
v->cur->pic_end = end;
else
if (!rh264_ctx_queue_slice(v->cur, &b, &sh, 0, v->pps.entropy_coding_mode_flag))
{ v->cur->pic_open = 0; return -1; }
v->cur->vlc = v->vlc_ready ? &v->vlc : NULL;
if (!v->threaded)
rh264_ctx_run_slices(v->cur, v->cur->vlc);
rc = v->cur->job_rc;
if (rc != 0)
v->cur->pic_open = 0;
return rc;
}
@@ -9385,7 +9601,7 @@ static void rh264_apply_list_mods(rh264_video *v, const rh264_frame **l,
}
}
static int rh264_video_decode_inter(rh264_video *v, const uint8_t *nal, size_t len)
static int rh264_video_decode_inter(rh264_video *v, const uint8_t *nal, size_t len, int *got_pic)
{
int nut, nri, rc, kind, end = 0;
size_t rl;
@@ -9393,7 +9609,10 @@ static int rh264_video_decode_inter(rh264_video *v, const uint8_t *nal, size_t l
rh264_bits b;
rh264_slice_hdr *sh = &v->cur->sscr.sh;
if (len < 1) return -1;
if (!v->have_ref) return -1; /* need a decoded reference first */
/* A reference must exist before a P or B slice builds its lists -
* checked there rather than here: with pictures in flight the
* IDR before this picture enters the DPB when this picture opens,
* which is after the header below, not before it. */
nut = nal[0] & 0x1f;
nri = (nal[0] >> 5) & 3;
rbsp = rh264_unescape_scratch(v, nal + 1, len - 1, &rl);
@@ -9413,8 +9632,10 @@ static int rh264_video_decode_inter(rh264_video *v, const uint8_t *nal, size_t l
int fld = sh->field_pic_flag ? (sh->bottom_field_flag ? 2 : 1) : 0;
int second = fld && v->pair_open && v->pair_frame_num == sh->frame_num
&& v->cur_field && v->cur_field != fld;
if (!second && rh264_video_next_ctx(v, got_pic) != 0) return -1;
v->cur->pic_open = 0;
if (!second && rh264_video_next_ctx(v) != 0) return -1;
v->cur->sps = v->sps;
v->cur->pps = v->pps;
v->last_poc = rh264_derive_poc(v, sh, nri);
v->cur_field = fld;
rh264_frame_set_field(&v->cur->f, fld);
@@ -9427,10 +9648,12 @@ static int rh264_video_decode_inter(rh264_video *v, const uint8_t *nal, size_t l
v->cur->pic_open = 1; v->cur->pic_kind = kind; v->cur->pic_ref = nri;
v->cur->pic_end = 0; v->cur->pic_nslices = 0;
v->cur->f.rows_deblocked = 0;
v->cur->f.row_user = v;
v->cur->f.row_user = v->cur;
v->cur->f.row_done = (v->cur->f.mbaff || v->cur->f.field) ? NULL : rh264_video_row_done;
if (v->cur->f.planes)
rh264_block_of(v->cur->f.planes)->rows_final = 0;
rh264_block_publish_rows(v->cur->f.planes, 0);
v->cur->job_rc = 0;
v->cur->completed = 0;
rh264_resolve_scaling(&v->sps, &v->pps, v->cur->f.w4, v->cur->f.w8);
}
else if (!v->cur->pic_open || v->cur->pic_kind != kind
@@ -9445,12 +9668,13 @@ static int rh264_video_decode_inter(rh264_video *v, const uint8_t *nal, size_t l
* with normal inter-picture bookkeeping -- the reference buffer stays,
* the counters keep running, and the picture is stored like any other
* reference. */
if (v->pps.entropy_coding_mode_flag)
rc = rh264_cabac_decode_islice(&b, &v->sps, &v->pps, sh, &v->cur->f,
&end, &v->cur->sscr.cb);
else
rc = rh264_decode_islice(&b, &v->sps, &v->pps, sh, &v->cur->f, &end);
if (rc == 0) v->cur->pic_end = end; else v->cur->pic_open = 0;
if (!rh264_ctx_queue_slice(v->cur, &b, sh, 0, v->pps.entropy_coding_mode_flag))
{ v->cur->pic_open = 0; return -1; }
v->cur->vlc = v->vlc_ready ? &v->vlc : NULL;
if (!v->threaded)
rh264_ctx_run_slices(v->cur, v->cur->vlc);
rc = v->cur->job_rc;
if (rc != 0) v->cur->pic_open = 0;
v->last_picnum = sh->frame_num_val;
v->pend_n_mmco = sh->n_mmco;
memcpy(v->pend_mmco_op, sh->mmco_op, sizeof(v->pend_mmco_op));
@@ -9458,6 +9682,7 @@ static int rh264_video_decode_inter(rh264_video *v, const uint8_t *nal, size_t l
memcpy(v->pend_mmco_b, sh->mmco_b, sizeof(v->pend_mmco_b));
return rc;
}
if (!v->have_ref && kind != 2) { v->cur->pic_open = 0; return -1; }
if (v->dpb_len < 1)
{ return -1; }
if (sh->slice_type == RH264_SLICE_B)
@@ -9602,18 +9827,18 @@ static int rh264_video_decode_inter(rh264_video *v, const uint8_t *nal, size_t l
bc->colg = bc->l1[0]->mvg;
if (!bc->colg) { return -1; }
rh264_b_setup_scales(bc);
if (v->pps.entropy_coding_mode_flag)
rc = rh264_cabac_decode_bslice(&b, &v->sps, &v->pps, sh, &v->cur->f,
bc, v->cur->mvg, &end, &v->cur->sscr.cb);
else
rc = rh264_decode_bslice(&b, &v->sps, &v->pps, sh, &v->cur->f, bc,
v->cur->mvg, &end);
if (rc == 0)
{
rh264_video_fold_mvg(v, sh->first_mb_in_slice, end);
v->cur->pic_end = end;
rh264_slice_job *j = rh264_ctx_queue_slice(v->cur, &b, sh, 2,
v->pps.entropy_coding_mode_flag);
if (!j) { return -1; }
j->bc = *bc;
}
else v->cur->pic_open = 0;
v->cur->vlc = v->vlc_ready ? &v->vlc : NULL;
if (!v->threaded)
rh264_ctx_run_slices(v->cur, v->cur->vlc);
rc = v->cur->job_rc;
(void)end;
if (rc != 0) v->cur->pic_open = 0;
v->last_picnum = sh->frame_num_val;
v->pend_n_mmco = sh->n_mmco;
memcpy(v->pend_mmco_op, sh->mmco_op, sizeof(v->pend_mmco_op));
@@ -9628,7 +9853,7 @@ static int rh264_video_decode_inter(rh264_video *v, const uint8_t *nal, size_t l
* list may name the same picture more than once, which is how weighted
* prediction offers a weighted and an unweighted version of it, so it
* can be longer than the number of pictures held. */
const rh264_frame *l0[34];
const rh264_frame **l0 = v->cur->sscr.l0;
int *lpn = v->cur->sscr.lpn;
int *l0poc = v->cur->sscr.l0poc;
signed char *picid = v->cur->sscr.picid;
@@ -9682,19 +9907,22 @@ static int rh264_video_decode_inter(rh264_video *v, const uint8_t *nal, size_t l
}
(void)lpn;
rh264_ctx_snapshot_list(v->cur, l0, nref);
if (v->pps.entropy_coding_mode_flag)
rc = rh264_cabac_decode_pslice(&b, &v->sps, &v->pps, sh, &v->cur->f,
l0, nref, picid, l0poc, v->cur->mvg, &end, &v->cur->sscr.cb);
else
rc = rh264_decode_pslice(&b, &v->sps, &v->pps, sh, &v->cur->f,
l0, nref, picid, l0poc, v->cur->mvg, &end);
{
rh264_slice_job *j = rh264_ctx_queue_slice(v->cur, &b, sh, 1,
v->pps.entropy_coding_mode_flag);
if (!j) { v->cur->pic_open = 0; return -1; }
j->nref = nref;
memcpy(j->l0, l0, sizeof(j->l0[0]) * nref);
memcpy(j->picid, picid, sizeof(j->picid[0]) * nref);
memcpy(j->l0poc, l0poc, sizeof(j->l0poc[0]) * nref);
}
v->cur->vlc = v->vlc_ready ? &v->vlc : NULL;
if (!v->threaded)
rh264_ctx_run_slices(v->cur, v->cur->vlc);
rc = v->cur->job_rc;
}
if (rc == 0)
{
rh264_video_fold_mvg(v, sh->first_mb_in_slice, end);
v->cur->pic_end = end;
}
else v->cur->pic_open = 0;
(void)end;
if (rc != 0) v->cur->pic_open = 0;
v->last_picnum = sh->frame_num_val;
v->pend_n_mmco = sh->n_mmco;
memcpy(v->pend_mmco_op, sh->mmco_op, sizeof(v->pend_mmco_op));
@@ -9718,13 +9946,18 @@ static int rh264_video_decode_inter(rh264_video *v, const uint8_t *nal, size_t l
* finish takes the rest. */
static void rh264_video_row_done(void *user, int mby)
{
rh264_video *v = (rh264_video*)user;
if (!v || !v->cur->pic_open || v->cur->pic_nslices != 1 || mby < 1)
/* The context, not the decoder: on a pool thread the decoder's
* current picture is a later one. */
rh264_pic_ctx *c = (rh264_pic_ctx*)user;
/* pic_open is the sequence's flag and the sequence may have closed
* this picture already - it closes it when the next one opens,
* which on a pool thread is while this one still decodes. */
if (!c || c->pic_nslices != 1 || mby < 1)
return;
if (v->cur->f.rows_deblocked != mby - 1)
if (c->f.rows_deblocked != mby - 1)
return; /* a gap: leave it to the finish */
if (v->cur->pic_kind <= 2)
rh264_deblock(&v->cur->f, v->cur->pic_idc, v->cur->pic_oA, v->cur->pic_oB,
if (c->pic_kind <= 2)
rh264_deblock(&c->f, c->pic_idc, c->pic_oA, c->pic_oB,
mby - 1, mby);
else
{
@@ -9734,17 +9967,21 @@ static void rh264_video_row_done(void *user, int mby)
* once each slice is done, and mid-slice it still holds the
* last picture's. The finish reads pic_mvg because by then they
* are the same. */
if (!v->cur->mvg)
if (!c->mvg)
return;
rh264_deblock_pslice(&v->cur->f, v->cur->pic_idc, v->cur->pic_oA, v->cur->pic_oB,
v->cur->mvg, mby - 1, mby);
rh264_deblock_pslice(&c->f, c->pic_idc, c->pic_oA, c->pic_oB,
c->mvg, mby - 1, mby);
}
v->cur->f.rows_deblocked = mby;
c->f.rows_deblocked = mby;
/* The row's motion goes into the picture grid with it, so a later
* picture's temporal direct finds the co-located cells final along
* with the samples. */
rh264_ctx_fold_mvg(c, (mby - 1) * c->f.mbw, mby * c->f.mbw);
/* Row mby-1 is filtered; filtering row mby will still touch its
* last lines through their shared edge, so what is final is
* everything above it. */
if (v->cur->f.planes)
rh264_block_of(v->cur->f.planes)->rows_final = mby - 1;
if (c->f.planes)
rh264_block_publish_rows(c->f.planes, mby - 1);
}
/* The picture's own completion: the slice map for the filter, the rows
@@ -9789,10 +10026,154 @@ static void rh264_ctx_complete_picture(rh264_pic_ctx *c)
c->f.rows_deblocked = 0;
c->f.row_done = NULL;
if (c->f.planes)
rh264_block_of(c->f.planes)->rows_final = c->f.mbh;
rh264_block_publish_rows(c->f.planes, c->f.mbh);
rh264_ctx_release_refs(c);
}
/* Queue one slice's data decode on the picture's context. Takes its
* own copy of the RBSP; the lists point at the context's snapshots,
* which outlive the queue. */
static rh264_slice_job *rh264_ctx_queue_slice(rh264_pic_ctx *c,
const rh264_bits *b, const rh264_slice_hdr *sh, int kind, int cabac)
{
rh264_slice_job *j = (rh264_slice_job*)calloc(1, sizeof(*j));
if (!j)
return NULL;
j->rbsp = (uint8_t*)malloc(b->size ? b->size : 1);
if (!j->rbsp)
{
free(j);
return NULL;
}
memcpy(j->rbsp, b->buf, b->size);
j->rbsp_len = b->size;
j->bitpos = b->bitpos;
j->sh = *sh;
j->kind = kind;
j->cabac = cabac;
if (c->jobs_tail)
c->jobs_tail->next = j;
else
c->jobs = j;
c->jobs_tail = j;
return j;
}
/* Drain the context's slice queue in order: each slice's data decode,
* the fold of its motion into the picture grid, the end of the coded
* area; and, once the slices cover the picture, its completion. This
* is the picture's work and reads nothing of the sequence beyond the
* inverse VLC tables, which are read-only for the decoder's life.
* Runs on the submitting thread today; on a pool thread once pictures
* decode concurrently. */
static void rh264_ctx_run_slices(rh264_pic_ctx *c, const struct rh264_vlc_set *vlc)
{
int total = c->f.mbw * c->f.mbh;
while (c->jobs)
{
rh264_slice_job *j = c->jobs;
rh264_bits b;
int end = 0, rc = -1;
c->jobs = j->next;
if (!c->jobs)
c->jobs_tail = NULL;
if (c->job_rc == 0)
{
rh264_bits_init(&b, j->rbsp, j->rbsp_len);
b.bitpos = j->bitpos;
if (vlc) b.vlc = vlc;
switch (j->kind)
{
case 0:
rc = j->cabac
? rh264_cabac_decode_islice(&b, &c->sps, &c->pps, &j->sh, &c->f, &end, &c->sscr.cb)
: rh264_decode_islice(&b, &c->sps, &c->pps, &j->sh, &c->f, &end);
break;
case 1:
rc = j->cabac
? rh264_cabac_decode_pslice(&b, &c->sps, &c->pps, &j->sh, &c->f,
j->l0, j->nref, j->picid, j->l0poc, c->mvg, &end, &c->sscr.cb)
: rh264_decode_pslice(&b, &c->sps, &c->pps, &j->sh, &c->f,
j->l0, j->nref, j->picid, j->l0poc, c->mvg, &end);
break;
default:
rc = j->cabac
? rh264_cabac_decode_bslice(&b, &c->sps, &c->pps, &j->sh, &c->f,
&j->bc, c->mvg, &end, &c->sscr.cb)
: rh264_decode_bslice(&b, &c->sps, &c->pps, &j->sh, &c->f, &j->bc,
c->mvg, &end);
break;
}
if (rc == 0)
{
if (j->kind != 0)
rh264_ctx_fold_mvg(c, j->sh.first_mb_in_slice, end);
c->pic_end = end;
}
else
c->job_rc = rc;
}
free(j->rbsp);
free(j);
}
/* Completion is the picture's own affair, not the sequence's: it
* does not ask pic_open, which the sequence clears when the next
* picture opens, while this one may still be on a pool thread. */
if (c->job_rc == 0 && c->pic_end >= total && !c->completed)
{
rh264_ctx_complete_picture(c);
c->completed = 1;
}
}
#ifdef HAVE_THREADS
static void rh264_ctx_job(void *arg)
{
rh264_pic_ctx *c = (rh264_pic_ctx*)arg;
rh264_ctx_run_slices(c, c->vlc);
if (c->job_rc != 0 && c->f.planes)
rh264_block_publish_rows(c->f.planes, c->f.mbh); /* nobody waits on a refusal */
slock_lock(rh264_rows_lock);
retro_atomic_store_release_int(&c->busy, 0);
scond_broadcast(rh264_rows_cond);
slock_unlock(rh264_rows_lock);
}
/* Wait for the context's picture, if a pool thread still has it. */
static void rh264_ctx_join(rh264_pic_ctx *c)
{
if (!rh264_rows_lock)
return;
slock_lock(rh264_rows_lock);
while (retro_atomic_load_acquire_int(&c->busy))
scond_wait(rh264_rows_cond, rh264_rows_lock);
slock_unlock(rh264_rows_lock);
}
#endif
/* Hand the current picture's queued slices to the pool, or run them
* here: a field picture stays on this thread for its pair, and so does
* an intra picture, whose grid is marked at completion. Nothing is
* queued for the picture after this. */
static void rh264_video_post_picture(rh264_video *v)
{
rh264_pic_ctx *c = v->cur;
if (!c->pic_open || c->posted)
return;
c->posted = 1;
#ifdef HAVE_THREADS
if (v->threaded && !v->cur_field && c->pic_kind > 2)
{
retro_atomic_store_release_int(&c->busy, 1);
if (tpool_add_work((tpool_t*)v->pool, rh264_ctx_job, c))
return;
retro_atomic_store_release_int(&c->busy, 0);
}
#endif
rh264_ctx_run_slices(c, c->vlc);
}
/* The sequence's side of a finished picture: an IDR empties the DPB,
* reference marking runs, the picture enters the DPB as a reference
* and the output queue as a picture to show. Runs in decode order on
@@ -9802,7 +10183,11 @@ static void rh264_ctx_complete_picture(rh264_pic_ctx *c)
static void rh264_video_finish_picture(rh264_video *v, int *got_pic)
{
if (!v->cur->pic_open) return;
rh264_ctx_complete_picture(v->cur);
if (!v->threaded && !v->cur->completed)
{
rh264_ctx_complete_picture(v->cur);
v->cur->completed = 1;
}
if (v->cur->pic_kind == 1)
{
v->dpb_len = 0; /* an IDR empties the reference list (8.2.5.1) */
@@ -9867,11 +10252,13 @@ static int rh264_video_handle_slice_nal(rh264_video *v, const uint8_t *nal,
return 0;
if (rh264_frame_alloc_if_needed(v) != 0) return -1;
if (type == 5)
{ if (rh264_video_decode_idr(v, nal, nl) != 0) return -1; }
{ if (rh264_video_decode_idr(v, nal, nl, got_pic) != 0) return -1; }
else
{ if (rh264_video_decode_inter(v, nal, nl) != 0) return -1; }
{ if (rh264_video_decode_inter(v, nal, nl, got_pic) != 0) return -1; }
/* a picture completes when its slices cover every macroblock; until
* then further slice NAL units of the same picture are expected */
if (v->threaded)
return *got_pic ? 1 : 0; /* the open of this picture may have shown one */
if (v->cur->pic_open && v->cur->pic_end >= v->cur->f.mbw * v->cur->f.mbh)
{
rh264_video_finish_picture(v, got_pic);
@@ -9957,6 +10344,48 @@ void rh264_video_set_contexts(rh264_video *v, int n)
v->nctx = n;
}
void rh264_video_set_publish_delay(int max_us)
{
#ifdef HAVE_THREADS
rh264_publish_delay_us = max_us > 0 ? max_us : 0;
#else
(void)max_us;
#endif
}
void rh264_video_set_thread_pool(rh264_video *v, void *pool, int threads)
{
#ifdef HAVE_THREADS
if (!v)
return;
if (!pool || threads < 2)
{
v->pool = NULL;
v->threaded = 0;
return;
}
if (!rh264_rows_lock)
{
rh264_rows_lock = slock_new();
rh264_rows_cond = scond_new();
rh264_blocks_lock = slock_new();
if (!rh264_rows_lock || !rh264_rows_cond || !rh264_blocks_lock)
{
if (rh264_rows_lock) slock_free(rh264_rows_lock);
if (rh264_rows_cond) scond_free(rh264_rows_cond);
if (rh264_blocks_lock) slock_free(rh264_blocks_lock);
rh264_rows_lock = NULL; rh264_rows_cond = NULL; rh264_blocks_lock = NULL;
return;
}
}
rh264_video_set_contexts(v, threads > RH264_MAX_CTX ? RH264_MAX_CTX : threads);
v->pool = pool;
v->threaded = v->nctx > 1;
#else
(void)v; (void)pool; (void)threads;
#endif
}
int rh264_video_bit_depth(const rh264_video *v)
{
const rh264_frame *f;
@@ -9996,6 +10425,24 @@ int rh264_video_drain(rh264_video *v)
{
int i, bi = -1;
if (!v) return -1;
if (v->threaded)
{
/* The last picture never saw a next one open: it goes to the
* pool now and takes its place, and every picture in flight
* lands before the queue is read. */
int got = 0;
if (v->cur->pic_open)
{
rh264_video_post_picture(v);
rh264_video_finish_picture(v, &got);
if (got)
return 0;
}
#ifdef HAVE_THREADS
for (i = 0; i < v->nctx; i++)
rh264_ctx_join(&v->ctx[i]);
#endif
}
for (i = 0; i < RH264_OUT_SLOTS; i++)
{
if (!v->out_used[i]) continue;
+2
View File
@@ -36,6 +36,8 @@
* (paths are treated as filesystem paths), and duplicate detection.
*/
#include <retro_posix_source.h>
#include <retro_miscellaneous.h>
#include <string/stdstring.h>
+7
View File
@@ -513,6 +513,8 @@ static bool rmp4_video_stream_open_decoder(rmp4_video_stream_t *s)
if (!(s->h264 = rh264_video_open()))
return false;
rh264_video_set_skip_nonref(s->h264, s->catchup);
if (s->blit_pool && s->blit_bands > 1)
rh264_video_set_thread_pool(s->h264, s->blit_pool, (int)s->blit_bands);
if (t && t->codec_private && t->codec_private_size)
rh264_video_set_extradata(s->h264, t->codec_private,
t->codec_private_size);
@@ -1050,6 +1052,11 @@ void rmp4_video_stream_set_blit_pool(rmp4_video_stream_t *s,
/* H.265 WPP rows likewise. */
if (s->h265)
rh265_video_set_thread_pool(s->h265, pool, bands);
/* H.264 has no rows to split; its pictures decode concurrently
* instead, one per thread, each waiting on the rows of the ones it
* predicts from. */
if (s->h264)
rh264_video_set_thread_pool(s->h264, pool, (int)bands);
}
/* The blit as a row-band job (image_blit_bands): every parameter of
+15
View File
@@ -100,6 +100,21 @@ int rh264_video_ref_wait_misses(void);
* pictures decode concurrently. */
void rh264_video_set_contexts(rh264_video *v, int n);
/* Decode pictures concurrently on @pool (a tpool_t), up to @threads at
* a time: a picture's slices are handed to the pool when the next
* picture opens, and a picture reading from one still decoding waits
* for the rows it needs and no more. Output is the same, in the same
* order, later by the pictures in flight; rh264_video_drain() lands
* them all. NULL, or fewer than two, is the decoder on the calling
* thread. No effect without HAVE_THREADS. */
void rh264_video_set_thread_pool(rh264_video *v, void *pool, int threads);
/* Test knob: hold each row's publication for up to @max_yields thread
* yields, drawn at random, so that pictures reading from a picture in flight
* wait for their rows rather than nearly always finding them. Output
* must be byte-exact under it. 0 is off. Debug only. */
void rh264_video_set_publish_delay(int max_yields);
int rh264_video_bit_depth(const rh264_video *v);
/* Borrow a decoded plane (0=Y, 1=U, 2=V). Valid until the next decode call. */
+5 -1
View File
@@ -80,7 +80,11 @@ typedef __int64 ssize_t;
typedef int ssize_t;
#endif
#endif
#elif defined(__MACH__) && defined(__APPLE__)
#elif defined(__unix__) || (defined(__MACH__) && defined(__APPLE__)) \
|| defined(__HAIKU__)
/* POSIX home of ssize_t; <stdio.h> carries it only for a profile asking
* for the BSD or POSIX.1-2008 set. Console toolchains keep the arms
* above, as they do not define __unix__. */
#include <sys/types.h>
#endif
+65
View File
@@ -0,0 +1,65 @@
/* Copyright (C) 2010-2020 The RetroArch team
*
* ---------------------------------------------------------------------------------------
* The following license statement only applies to this file (retro_posix_source.h).
* ---------------------------------------------------------------------------------------
*
* Permission is hereby granted, free of charge,
* to any person obtaining a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef __LIBRETRO_SDK_POSIX_SOURCE_H
#define __LIBRETRO_SDK_POSIX_SOURCE_H
/* The feature profile these sources are written against: strdup(),
* strcasecmp(), fstatat(), O_CLOEXEC, M_PI, sigaltstack() and syscall()
* all sit behind __USE_XOPEN2K8 or __USE_MISC, and a consumer is free to
* build the library under a stricter profile than the default -- the
* Debian/Launchpad core packaging passes -D_XOPEN_SOURCE=600.
*
* MUST BE THE FIRST INCLUDE: a libc settles what its headers expose when
* the first of them is seen.
*
* The guards only raise the profile, and ask for what a build passing no
* feature macros already gets, so nothing this library selects changes.
* Linux only: elsewhere _POSIX_C_SOURCE would instead hide extensions
* these sources use. */
/* Opt-in, because a unity build reaches its second file with system
* headers already in scope, where these macros are inert rather than
* wrong. __GLIBC__ arrives from <features.h>. */
#if defined(RETRO_POSIX_SOURCE_CHECK_ORDER) && defined(__GLIBC__)
#error "retro_posix_source.h must be included before any system header"
#endif
#if defined(__linux__) && !defined(_GNU_SOURCE)
#if !defined(_POSIX_C_SOURCE) || (_POSIX_C_SOURCE - 0) < 200809L
#undef _POSIX_C_SOURCE
#define _POSIX_C_SOURCE 200809L
#endif
#if defined(_XOPEN_SOURCE) && (_XOPEN_SOURCE - 0) < 700
#undef _XOPEN_SOURCE
#define _XOPEN_SOURCE 700
#endif
#ifndef _DEFAULT_SOURCE
#define _DEFAULT_SOURCE 1
#endif
#endif
#endif
+2
View File
@@ -12,6 +12,8 @@
*/
#define LIBCO_C
#include <retro_posix_source.h>
#include <libco.h>
#include <stdlib.h>
#include <signal.h>
+2
View File
@@ -20,6 +20,8 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include <retro_posix_source.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
+2
View File
@@ -20,6 +20,8 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include <retro_posix_source.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+2
View File
@@ -20,6 +20,8 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include <retro_posix_source.h>
#include <string.h>
#include <compat/strl.h>
+2
View File
@@ -20,6 +20,8 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include <retro_posix_source.h>
#include <stdint.h>
#include <string.h>
#include <ctype.h>
+2
View File
@@ -20,6 +20,8 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include <retro_posix_source.h>
#include <media/media_detect_cd.h>
#include <streams/file_stream.h>
#include <string/stdstring.h>
+2
View File
@@ -20,6 +20,8 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include <retro_posix_source.h>
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
+2
View File
@@ -20,6 +20,8 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include <retro_posix_source.h>
#include <string.h>
#include <compat/strl.h>
+2
View File
@@ -48,6 +48,8 @@
* kick_process(), which is an IPI.
*/
#include <retro_posix_source.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
@@ -193,6 +193,70 @@ static void test_exact_fit_no_extension(void)
free(region);
}
/* fill_pathname_join() reports the length it wanted, so >= len is how a
* caller detects truncation. Callers that size a path buffer from a
* bound held somewhere else rely on that, and on nothing outside the
* buffer being touched when the join does not fit. */
static void test_join_truncation(void)
{
char *region = (char*)malloc(DST_LEN + GUARD_LEN);
char dir[DST_LEN];
size_t want;
size_t i;
if (!region)
abort();
memset(region, SENTINEL, DST_LEN + GUARD_LEN);
for (i = 0; i < DST_LEN - 1; i++)
dir[i] = 'd';
dir[DST_LEN - 1] = '\0';
want = fill_pathname_join(region, dir, "hires.txt", DST_LEN);
if (want < DST_LEN)
{
printf("[FAILED] fill_pathname_join reported %zu for a join that cannot fit in %d\n",
want, DST_LEN);
failures++;
}
else
printf("[SUCCESS] oversized join reported truncation (%zu >= %d)\n",
want, DST_LEN);
for (i = DST_LEN; i < (size_t)(DST_LEN + GUARD_LEN); i++)
{
if ((unsigned char)region[i] != SENTINEL)
{
printf("[FAILED] fill_pathname_join overran dst: byte %zu changed\n", i);
failures++;
break;
}
}
if (i == (size_t)(DST_LEN + GUARD_LEN))
printf("[SUCCESS] oversized join did not overrun destination\n");
if (region[DST_LEN - 1] != '\0')
{
printf("[FAILED] fill_pathname_join left dst unterminated\n");
failures++;
}
/* A join that fits reports its own length and inserts one separator. */
memset(region, SENTINEL, DST_LEN + GUARD_LEN);
want = fill_pathname_join(region, "/packs/game.hdpack", "tiles.png", DST_LEN);
if (want != strlen("/packs/game.hdpack/tiles.png")
|| strcmp(region, "/packs/game.hdpack/tiles.png"))
{
printf("[FAILED] fitting join gave \"%s\" (%zu)\n", region, want);
failures++;
}
else
printf("[SUCCESS] fitting join reported its own length\n");
free(region);
}
int main(void)
{
/* Documented semantics. */
@@ -211,6 +275,7 @@ int main(void)
test_overlong_input_with_dot();
test_exact_fit_no_extension();
test_overlong_input_no_dot();
test_join_truncation();
if (failures)
{
+7 -3
View File
@@ -5,11 +5,15 @@ LIBRETRO_COMM_DIR := ../../..
SOURCES := \
rh264_lossless_test.c \
$(LIBRETRO_COMM_DIR)/formats/mp4/rmp4.c \
$(LIBRETRO_COMM_DIR)/formats/h264/rh264.c
$(LIBRETRO_COMM_DIR)/formats/h264/rh264.c \
$(LIBRETRO_COMM_DIR)/rthreads/rthreads.c \
$(LIBRETRO_COMM_DIR)/rthreads/tpool.c
OBJS := $(SOURCES:.c=.o)
CFLAGS += -Wall -std=gnu99 -g -O1 -DHAVE_RMP4 -DHAVE_RH264 -D_GNU_SOURCE \
# HAVE_THREADS: the oracle decodes every case on a pool as well, and
# the threaded output has to be the single-thread output to the byte.
CFLAGS += -Wall -std=gnu99 -g -O1 -DHAVE_RMP4 -DHAVE_RH264 -DHAVE_THREADS -D_GNU_SOURCE \
-I$(LIBRETRO_COMM_DIR)/include
# The samples workflow passes SANITIZER=address,undefined to every
@@ -19,7 +23,7 @@ ifneq ($(SANITIZER),)
LDFLAGS := -fsanitize=$(SANITIZER) $(LDFLAGS)
endif
LDLIBS += -lm
LDLIBS += -lm -lpthread
all: $(TARGET)
@@ -39,6 +39,7 @@
#include <formats/rmp4.h>
#include <formats/rh264.h>
#include <rthreads/tpool.h>
static int fails;
static char dir[256];
@@ -98,6 +99,13 @@ static int run(const char *fmt, ...)
* and would be shared between pictures decoding concurrently. Every
* case is decoded at both. */
static int g_contexts = 1;
/* Pool threads for the decode under test; 0 is the calling thread. With
* a pool the pictures decode concurrently, a picture reading from one
* still decoding waits for the rows it needs, and the output must be
* the single-thread output to the byte - a difference is a row read
* before it was final, or published before it was. */
static int g_threads = 0;
static void *g_pool = NULL;
static long compare(const char *mp4, const uint8_t *ref_a, size_t alen,
const uint8_t *ref_b, int split, int *frames_out)
@@ -126,6 +134,8 @@ static long compare(const char *mp4, const uint8_t *ref_a, size_t alen,
h = rh264_video_open();
if (h && g_contexts > 1)
rh264_video_set_contexts(h, g_contexts);
if (h && g_threads > 1 && g_pool)
rh264_video_set_thread_pool(h, g_pool, g_threads);
if (trk < 0 || !h || rh264_video_set_extradata(h,
rmp4_get_track(m, trk)->codec_private,
rmp4_get_track(m, trk)->codec_private_size))
@@ -143,6 +153,8 @@ static long compare(const char *mp4, const uint8_t *ref_a, size_t alen,
if (pkt.track != trk)
continue;
got = rh264_video_decode(h, pkt.data, pkt.size);
if (getenv("RH264_TRACE"))
fprintf(stderr, "TRACE threads=%d sample=%zu got=%d\n", g_threads, pkt.size, got);
if (got < 0)
{
bad = -1;
@@ -247,6 +259,23 @@ static void oracle_case(const char *name, const char *src, int frames,
printf(" %d frames, %ld differing samples with 4 contexts in rotation\n",
nf, bad < 0 ? 0 : bad);
check(" same with 4 picture contexts in rotation", bad == 0 && nf == frames);
if (g_pool)
{
g_threads = 4;
bad = compare(mp4, ref, rlen, ref, 0, &nf);
printf(" %d frames, %ld differing samples with 4 pictures decoding concurrently\n",
nf, bad < 0 ? 0 : bad);
check(" same with 4 pictures decoding concurrently", bad == 0 && nf == frames);
/* and with every row's publication held back at random, so the
* readers wait for their rows instead of finding them */
rh264_video_set_publish_delay(200);
bad = compare(mp4, ref, rlen, ref, 0, &nf);
rh264_video_set_publish_delay(0);
g_threads = 0;
printf(" %d frames, %ld differing samples concurrently with rows held back\n",
nf, bad < 0 ? 0 : bad);
check(" same concurrently with every row's publication delayed", bad == 0 && nf == frames);
}
free(ref);
}
@@ -549,6 +578,9 @@ int main(void)
* and in CABAC the contexts still see it. Intra macroblocks amid
* inter ones in P / B pictures exercise every neighbour position. */
printf("constrained_intra_pred, byte-exact vs ffmpeg:\n");
g_pool = tpool_create_with_stack_size(3, 512 * 1024);
if (!g_pool)
printf("no thread pool: the concurrent decodes are skipped\n");
oracle_case("cip_cabac", "testsrc2=s=176x144:r=15", 8, "yuv420p", "-crf 20",
"-preset medium -x264-params constrained-intra=1");
oracle_case("cip_cavlc", "testsrc2=s=176x144:r=15", 8, "yuv420p", "-crf 20",
+2
View File
@@ -20,6 +20,8 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include <retro_posix_source.h>
#include <stdint.h>
#include <ctype.h>
#include <string.h>
+2
View File
@@ -10,6 +10,8 @@
* around the 1024 predictor and small AC -- as well as 1180's own uniform
* ranges, because an IDCT can be accurate on one and not the other.
*/
#include <retro_posix_source.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
+2
View File
@@ -20,6 +20,8 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include <retro_posix_source.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>