Android-x86
Fork
捐款

  • R/O
  • HTTP
  • SSH
  • HTTPS

external-alsa-lib: 提交

external/alsa-lib


Commit MetaInfo

修訂119d9c1678b1193f8b969a6483cae1f7bf95e609 (tree)
時間2021-06-17 18:40:38
作者Takashi Iwai <tiwai@suse...>
CommiterTakashi Iwai

Log Message

pcm: rate: Improve the support multiple formats

This patch extends the PCM rate plugin for allowing its converter
plugin to deal with multiple formats. Currently, the converter plugin
is allowed to take different formats only when convert callback is
defined. And for this way (so far only the standard linear rate
plugin does), all linear formats have to be handled, and it's
cumbersome.

OTOH, most other rate plugins are implemented with convert_s16
callback, which accepts only S16 format. This is often not ideal
because many converter engines can handle 32bit formats. Also, the
target format is often 32bit format, hence this would require
additional conversion even if the converter engine can output 32bit
natively.

In this patch, for addressing the problems above, the rate plugin API
is extended in the following way:

- The new get_supported_formats callback is added; this stores the bit

masks of the supported input and output formats, as well as the
behavior flags. Currently only linear formats are allowed.

- When the plugin accepts only the interleaved stream, set

SND_PCM_RATE_FLAG_INTERLEAVED flag bit. Otherwise the code has to
handle snd_pcm_channel_area at each call.

- When both input and output formats have to be identical, pass

SND_PCM_RATE_FLAG_SYNC_FORMATS flag bit.

- When the converter wants to process different formats, use convert

callback instead of convert_s16. You can put both in the ops for
compatibility, too.
The input and output formats are found in the info argument of init
callback.

- Now the PCM rate plugin core will skip the temporary buffer

allocation and conversions for pre- and post-process if not needed
(i.e. matching with the requested input or output format).

The rate plugin API version is bumped to 0x010003.

Signed-off-by: Takashi Iwai <tiwai@suse.de>

Change Summary

差異

--- a/include/pcm_rate.h
+++ b/include/pcm_rate.h
@@ -38,7 +38,7 @@ extern "C" {
3838 /**
3939 * Protocol version
4040 */
41-#define SND_PCM_RATE_PLUGIN_VERSION 0x010002
41+#define SND_PCM_RATE_PLUGIN_VERSION 0x010003
4242
4343 /** hw_params information for a single side */
4444 typedef struct snd_pcm_rate_side_info {
@@ -55,6 +55,11 @@ typedef struct snd_pcm_rate_info {
5555 unsigned int channels;
5656 } snd_pcm_rate_info_t;
5757
58+enum {
59+ SND_PCM_RATE_FLAG_INTERLEAVED = (1U << 0), /** only interleaved format */
60+ SND_PCM_RATE_FLAG_SYNC_FORMATS = (1U << 1), /** both input and output formats have to be identical */
61+};
62+
5863 /** Callback table of rate-converter */
5964 typedef struct snd_pcm_rate_ops {
6065 /**
@@ -114,6 +119,13 @@ typedef struct snd_pcm_rate_ops {
114119 * new ops since version 0x010002
115120 */
116121 void (*dump)(void *obj, snd_output_t *out);
122+ /**
123+ * get the supported input and output formats (optional);
124+ * new ops since version 0x010003
125+ */
126+ int (*get_supported_formats)(void *obj, uint64_t *in_formats,
127+ uint64_t *out_formats,
128+ unsigned int *flags);
117129 } snd_pcm_rate_ops_t;
118130
119131 /** open function type */
@@ -147,6 +159,28 @@ typedef struct snd_pcm_rate_old_ops {
147159 snd_pcm_uframes_t (*input_frames)(void *obj, snd_pcm_uframes_t frames);
148160 snd_pcm_uframes_t (*output_frames)(void *obj, snd_pcm_uframes_t frames);
149161 } snd_pcm_rate_old_ops_t;
162+
163+/* old rate_ops for protocol version 0x010002 */
164+typedef struct snd_pcm_rate_v2_ops {
165+ void (*close)(void *obj);
166+ int (*init)(void *obj, snd_pcm_rate_info_t *info);
167+ void (*free)(void *obj);
168+ void (*reset)(void *obj);
169+ int (*adjust_pitch)(void *obj, snd_pcm_rate_info_t *info);
170+ void (*convert)(void *obj,
171+ const snd_pcm_channel_area_t *dst_areas,
172+ snd_pcm_uframes_t dst_offset, unsigned int dst_frames,
173+ const snd_pcm_channel_area_t *src_areas,
174+ snd_pcm_uframes_t src_offset, unsigned int src_frames);
175+ void (*convert_s16)(void *obj, int16_t *dst, unsigned int dst_frames,
176+ const int16_t *src, unsigned int src_frames);
177+ snd_pcm_uframes_t (*input_frames)(void *obj, snd_pcm_uframes_t frames);
178+ snd_pcm_uframes_t (*output_frames)(void *obj, snd_pcm_uframes_t frames);
179+ unsigned int version;
180+ int (*get_supported_rates)(void *obj, unsigned int *rate_min,
181+ unsigned int *rate_max);
182+ void (*dump)(void *obj, snd_output_t *out);
183+} snd_pcm_rate_v2_ops_t;
150184 #endif
151185
152186 #ifdef __cplusplus
--- a/src/pcm/pcm_rate.c
+++ b/src/pcm/pcm_rate.c
@@ -62,18 +62,22 @@ struct _snd_pcm_rate {
6262 void *open_func;
6363 void *obj;
6464 snd_pcm_rate_ops_t ops;
65- unsigned int get_idx;
66- unsigned int put_idx;
67- int16_t *src_buf;
68- int16_t *dst_buf;
65+ unsigned int src_conv_idx;
66+ unsigned int dst_conv_idx;
67+ snd_pcm_channel_area_t *src_buf;
68+ snd_pcm_channel_area_t *dst_buf;
6969 int start_pending; /* start is triggered but not commited to slave */
7070 snd_htimestamp_t trigger_tstamp;
7171 unsigned int plugin_version;
7272 unsigned int rate_min, rate_max;
73+ snd_pcm_format_t orig_in_format;
74+ snd_pcm_format_t orig_out_format;
75+ uint64_t in_formats;
76+ uint64_t out_formats;
77+ unsigned int format_flags;
7378 };
7479
7580 #define SND_PCM_RATE_PLUGIN_VERSION_OLD 0x010001 /* old rate plugin */
76-
7781 #endif /* DOC_HIDDEN */
7882
7983 /* allocate a channel area and a temporary buffer for the given size */
@@ -274,12 +278,84 @@ static int snd_pcm_rate_hw_refine(snd_pcm_t *pcm,
274278 snd_pcm_generic_hw_refine);
275279 }
276280
281+/* evaluate the best matching available format to the given format */
282+static int get_best_format(uint64_t mask, snd_pcm_format_t orig)
283+{
284+ int pwidth = snd_pcm_format_physical_width(orig);
285+ int width = snd_pcm_format_width(orig);
286+ int signd = snd_pcm_format_signed(orig);
287+ int best_score = -1;
288+ int match = -1;
289+ int f, score;
290+
291+ for (f = 0; f <= SND_PCM_FORMAT_LAST; f++) {
292+ if (!(mask & (1ULL << f)))
293+ continue;
294+ score = 0;
295+ if (snd_pcm_format_linear(f)) {
296+ if (snd_pcm_format_physical_width(f) == pwidth)
297+ score++;
298+ if (snd_pcm_format_physical_width(f) >= pwidth)
299+ score++;
300+ if (snd_pcm_format_width(f) == width)
301+ score++;
302+ if (snd_pcm_format_signed(f) == signd)
303+ score++;
304+ }
305+ if (score > best_score) {
306+ match = f;
307+ best_score = score;
308+ }
309+ }
310+
311+ return match;
312+}
313+
314+/* set up the input and output formats from the available lists */
315+static int choose_preferred_format(snd_pcm_rate_t *rate)
316+{
317+ uint64_t in_mask = rate->in_formats;
318+ uint64_t out_mask = rate->out_formats;
319+ int in, out;
320+
321+ if (!in_mask || !out_mask)
322+ return 0;
323+
324+ if (rate->orig_in_format == rate->orig_out_format)
325+ if (in_mask & out_mask & (1ULL << rate->orig_in_format))
326+ return 0; /* nothing changed */
327+
328+ repeat:
329+ in = get_best_format(in_mask, rate->orig_in_format);
330+ out = get_best_format(out_mask, rate->orig_out_format);
331+ if (in < 0 || out < 0)
332+ return -ENOENT;
333+
334+ if ((rate->format_flags & SND_PCM_RATE_FLAG_SYNC_FORMATS) &&
335+ in != out) {
336+ if (out_mask & (1ULL << in))
337+ out = in;
338+ else if (in_mask & (1ULL << out))
339+ in = out;
340+ else {
341+ in_mask &= ~(1ULL << in);
342+ out_mask &= ~(1ULL << out);
343+ goto repeat;
344+ }
345+ }
346+
347+ rate->info.in.format = in;
348+ rate->info.out.format = out;
349+ return 0;
350+}
351+
277352 static int snd_pcm_rate_hw_params(snd_pcm_t *pcm, snd_pcm_hw_params_t * params)
278353 {
279354 snd_pcm_rate_t *rate = pcm->private_data;
280355 snd_pcm_t *slave = rate->gen.slave;
281356 snd_pcm_rate_side_info_t *sinfo, *cinfo;
282- unsigned int channels, cwidth, swidth, chn;
357+ unsigned int channels, cwidth, swidth, chn, acc;
358+ int need_src_buf, need_dst_buf;
283359 int err = snd_pcm_hw_params_slave(pcm, params,
284360 snd_pcm_rate_hw_refine_cchange,
285361 snd_pcm_rate_hw_refine_sprepare,
@@ -310,6 +386,9 @@ static int snd_pcm_rate_hw_params(snd_pcm_t *pcm, snd_pcm_hw_params_t * params)
310386 err = INTERNAL(snd_pcm_hw_params_get_channels)(params, &channels);
311387 if (err < 0)
312388 return err;
389+ err = INTERNAL(snd_pcm_hw_params_get_access)(params, &acc);
390+ if (err < 0)
391+ return err;
313392
314393 rate->info.channels = channels;
315394 sinfo->format = slave->format;
@@ -321,36 +400,80 @@ static int snd_pcm_rate_hw_params(snd_pcm_t *pcm, snd_pcm_hw_params_t * params)
321400 SNDMSG("rate plugin already in use");
322401 return -EBUSY;
323402 }
324- err = rate->ops.init(rate->obj, &rate->info);
325- if (err < 0)
326- return err;
327403
328404 rate->pareas = rate_alloc_tmp_buf(rate, cinfo->format, channels,
329405 cinfo->period_size);
330406 rate->sareas = rate_alloc_tmp_buf(rate, sinfo->format, channels,
331407 sinfo->period_size);
332- if (!rate->pareas || !rate->sareas)
333- goto error;
334-
335- if (rate->ops.convert_s16) {
336- rate->get_idx = snd_pcm_linear_get_index(rate->info.in.format, SND_PCM_FORMAT_S16);
337- rate->put_idx = snd_pcm_linear_put_index(SND_PCM_FORMAT_S16, rate->info.out.format);
338- free(rate->src_buf);
339- rate->src_buf = malloc(channels * rate->info.in.period_size * 2);
340- free(rate->dst_buf);
341- rate->dst_buf = malloc(channels * rate->info.out.period_size * 2);
342- if (! rate->src_buf || ! rate->dst_buf)
408+ if (!rate->pareas || !rate->sareas) {
409+ err = -ENOMEM;
410+ goto error_pareas;
411+ }
412+
413+ rate->orig_in_format = rate->info.in.format;
414+ rate->orig_out_format = rate->info.out.format;
415+ if (choose_preferred_format(rate) < 0) {
416+ SNDERR("No matching format in rate plugin");
417+ err = -EINVAL;
418+ goto error_pareas;
419+ }
420+
421+ err = rate->ops.init(rate->obj, &rate->info);
422+ if (err < 0)
423+ goto error_init;
424+
425+ rate_free_tmp_buf(&rate->src_buf);
426+ rate_free_tmp_buf(&rate->dst_buf);
427+
428+ need_src_buf = need_dst_buf = 0;
429+
430+ if ((rate->format_flags & SND_PCM_RATE_FLAG_INTERLEAVED) &&
431+ !(acc == SND_PCM_ACCESS_MMAP_INTERLEAVED ||
432+ acc == SND_PCM_ACCESS_RW_INTERLEAVED)) {
433+ need_src_buf = need_dst_buf = 1;
434+ } else {
435+ if (rate->orig_in_format != rate->info.in.format)
436+ need_src_buf = 1;
437+ if (rate->orig_out_format != rate->info.out.format)
438+ need_dst_buf = 1;
439+ }
440+
441+ if (need_src_buf) {
442+ rate->src_conv_idx =
443+ snd_pcm_linear_convert_index(rate->orig_in_format,
444+ rate->info.in.format);
445+ rate->src_buf = rate_alloc_tmp_buf(rate, rate->info.in.format,
446+ channels, rate->info.in.period_size);
447+ if (!rate->src_buf) {
448+ err = -ENOMEM;
343449 goto error;
450+ }
451+ }
452+
453+ if (need_dst_buf) {
454+ rate->dst_conv_idx =
455+ snd_pcm_linear_convert_index(rate->info.out.format,
456+ rate->orig_out_format);
457+ rate->dst_buf = rate_alloc_tmp_buf(rate, rate->info.out.format,
458+ channels, rate->info.out.period_size);
459+ if (!rate->dst_buf) {
460+ err = -ENOMEM;
461+ goto error;
462+ }
344463 }
345464
346465 return 0;
347466
348467 error:
349- rate_free_tmp_buf(&rate->pareas);
350- rate_free_tmp_buf(&rate->sareas);
468+ rate_free_tmp_buf(&rate->src_buf);
469+ rate_free_tmp_buf(&rate->dst_buf);
470+ error_init:
351471 if (rate->ops.free)
352472 rate->ops.free(rate->obj);
353- return -ENOMEM;
473+ error_pareas:
474+ rate_free_tmp_buf(&rate->pareas);
475+ rate_free_tmp_buf(&rate->sareas);
476+ return err;
354477 }
355478
356479 static int snd_pcm_rate_hw_free(snd_pcm_t *pcm)
@@ -361,9 +484,8 @@ static int snd_pcm_rate_hw_free(snd_pcm_t *pcm)
361484 rate_free_tmp_buf(&rate->sareas);
362485 if (rate->ops.free)
363486 rate->ops.free(rate->obj);
364- free(rate->src_buf);
365- free(rate->dst_buf);
366- rate->src_buf = rate->dst_buf = NULL;
487+ rate_free_tmp_buf(&rate->src_buf);
488+ rate_free_tmp_buf(&rate->dst_buf);
367489 return snd_pcm_hw_free(rate->gen.slave);
368490 }
369491
@@ -444,82 +566,6 @@ static int snd_pcm_rate_init(snd_pcm_t *pcm)
444566 return 0;
445567 }
446568
447-static void convert_to_s16(snd_pcm_rate_t *rate, int16_t *buf,
448- const snd_pcm_channel_area_t *areas,
449- snd_pcm_uframes_t offset, unsigned int frames,
450- unsigned int channels)
451-{
452-#ifndef DOC_HIDDEN
453-#define GET16_LABELS
454-#include "plugin_ops.h"
455-#undef GET16_LABELS
456-#endif /* DOC_HIDDEN */
457- void *get = get16_labels[rate->get_idx];
458- const char *src;
459- int16_t sample;
460- const char *srcs[channels];
461- int src_step[channels];
462- unsigned int c;
463-
464- for (c = 0; c < channels; c++) {
465- srcs[c] = snd_pcm_channel_area_addr(areas + c, offset);
466- src_step[c] = snd_pcm_channel_area_step(areas + c);
467- }
468-
469- while (frames--) {
470- for (c = 0; c < channels; c++) {
471- src = srcs[c];
472- goto *get;
473-#ifndef DOC_HIDDEN
474-#define GET16_END after_get
475-#include "plugin_ops.h"
476-#undef GET16_END
477-#endif /* DOC_HIDDEN */
478- after_get:
479- *buf++ = sample;
480- srcs[c] += src_step[c];
481- }
482- }
483-}
484-
485-static void convert_from_s16(snd_pcm_rate_t *rate, const int16_t *buf,
486- const snd_pcm_channel_area_t *areas,
487- snd_pcm_uframes_t offset, unsigned int frames,
488- unsigned int channels)
489-{
490-#ifndef DOC_HIDDEN
491-#define PUT16_LABELS
492-#include "plugin_ops.h"
493-#undef PUT16_LABELS
494-#endif /* DOC_HIDDEN */
495- void *put = put16_labels[rate->put_idx];
496- char *dst;
497- int16_t sample;
498- char *dsts[channels];
499- int dst_step[channels];
500- unsigned int c;
501-
502- for (c = 0; c < channels; c++) {
503- dsts[c] = snd_pcm_channel_area_addr(areas + c, offset);
504- dst_step[c] = snd_pcm_channel_area_step(areas + c);
505- }
506-
507- while (frames--) {
508- for (c = 0; c < channels; c++) {
509- dst = dsts[c];
510- sample = *buf++;
511- goto *put;
512-#ifndef DOC_HIDDEN
513-#define PUT16_END after_put
514-#include "plugin_ops.h"
515-#undef PUT16_END
516-#endif /* DOC_HIDDEN */
517- after_put:
518- dsts[c] += dst_step[c];
519- }
520- }
521-}
522-
523569 static void do_convert(const snd_pcm_channel_area_t *dst_areas,
524570 snd_pcm_uframes_t dst_offset, unsigned int dst_frames,
525571 const snd_pcm_channel_area_t *src_areas,
@@ -527,28 +573,40 @@ static void do_convert(const snd_pcm_channel_area_t *dst_areas,
527573 unsigned int channels,
528574 snd_pcm_rate_t *rate)
529575 {
530- if (rate->ops.convert_s16) {
531- const int16_t *src;
532- int16_t *dst;
533- if (! rate->src_buf)
534- src = (int16_t *)src_areas->addr + src_offset * channels;
535- else {
536- convert_to_s16(rate, rate->src_buf, src_areas, src_offset,
537- src_frames, channels);
538- src = rate->src_buf;
539- }
540- if (! rate->dst_buf)
541- dst = (int16_t *)dst_areas->addr + dst_offset * channels;
542- else
543- dst = rate->dst_buf;
544- rate->ops.convert_s16(rate->obj, dst, dst_frames, src, src_frames);
545- if (dst == rate->dst_buf)
546- convert_from_s16(rate, rate->dst_buf, dst_areas, dst_offset,
547- dst_frames, channels);
576+ const snd_pcm_channel_area_t *out_areas;
577+ snd_pcm_uframes_t out_offset;
578+
579+ if (rate->dst_buf) {
580+ out_areas = rate->dst_buf;
581+ out_offset = 0;
548582 } else {
549- rate->ops.convert(rate->obj, dst_areas, dst_offset, dst_frames,
550- src_areas, src_offset, src_frames);
583+ out_areas = dst_areas;
584+ out_offset = dst_offset;
585+ }
586+
587+ if (rate->src_buf) {
588+ snd_pcm_linear_convert(rate->src_buf, 0,
589+ src_areas, src_offset,
590+ channels, src_frames,
591+ rate->src_conv_idx);
592+ src_areas = rate->src_buf;
593+ src_offset = 0;
551594 }
595+
596+ if (rate->ops.convert)
597+ rate->ops.convert(rate->obj, out_areas, out_offset, dst_frames,
598+ src_areas, src_offset, src_frames);
599+ else
600+ rate->ops.convert_s16(rate->obj,
601+ snd_pcm_channel_area_addr(out_areas, out_offset),
602+ dst_frames,
603+ snd_pcm_channel_area_addr(src_areas, src_offset),
604+ src_frames);
605+ if (rate->dst_buf)
606+ snd_pcm_linear_convert(dst_areas, dst_offset,
607+ rate->dst_buf, 0,
608+ channels, dst_frames,
609+ rate->dst_conv_idx);
552610 }
553611
554612 static inline void
@@ -1276,6 +1334,30 @@ const snd_config_t *snd_pcm_rate_get_default_converter(snd_config_t *root)
12761334 return NULL;
12771335 }
12781336
1337+static void rate_initial_setup(snd_pcm_rate_t *rate)
1338+{
1339+ if (rate->plugin_version == SND_PCM_RATE_PLUGIN_VERSION)
1340+ rate->plugin_version = rate->ops.version;
1341+
1342+ if (rate->plugin_version >= 0x010002 &&
1343+ rate->ops.get_supported_rates)
1344+ rate->ops.get_supported_rates(rate->obj,
1345+ &rate->rate_min,
1346+ &rate->rate_max);
1347+
1348+ if (rate->plugin_version >= 0x010003 &&
1349+ rate->ops.get_supported_formats) {
1350+ rate->ops.get_supported_formats(rate->obj,
1351+ &rate->in_formats,
1352+ &rate->out_formats,
1353+ &rate->format_flags);
1354+ } else if (!rate->ops.convert && rate->ops.convert_s16) {
1355+ rate->in_formats = rate->out_formats =
1356+ 1ULL << SND_PCM_FORMAT_S16;
1357+ rate->format_flags = SND_PCM_RATE_FLAG_INTERLEAVED;
1358+ }
1359+}
1360+
12791361 #ifdef PIC
12801362 static int is_builtin_plugin(const char *type)
12811363 {
@@ -1301,20 +1383,11 @@ static int rate_open_func(snd_pcm_rate_t *rate, const char *type, const snd_conf
13011383 lib = lib_name;
13021384 }
13031385
1304- rate->rate_min = SND_PCM_PLUGIN_RATE_MIN;
1305- rate->rate_max = SND_PCM_PLUGIN_RATE_MAX;
1306- rate->plugin_version = SND_PCM_RATE_PLUGIN_VERSION;
1307-
13081386 open_conf_func = snd_dlobj_cache_get(lib, open_conf_name, NULL, verbose && converter_conf != NULL);
13091387 if (open_conf_func) {
13101388 err = open_conf_func(SND_PCM_RATE_PLUGIN_VERSION,
13111389 &rate->obj, &rate->ops, converter_conf);
13121390 if (!err) {
1313- rate->plugin_version = rate->ops.version;
1314- if (rate->ops.get_supported_rates)
1315- rate->ops.get_supported_rates(rate->obj,
1316- &rate->rate_min,
1317- &rate->rate_max);
13181391 rate->open_func = open_conf_func;
13191392 return 0;
13201393 } else {
@@ -1330,23 +1403,18 @@ static int rate_open_func(snd_pcm_rate_t *rate, const char *type, const snd_conf
13301403 rate->open_func = open_func;
13311404
13321405 err = open_func(SND_PCM_RATE_PLUGIN_VERSION, &rate->obj, &rate->ops);
1333- if (!err) {
1334- rate->plugin_version = rate->ops.version;
1335- if (rate->ops.get_supported_rates)
1336- rate->ops.get_supported_rates(rate->obj,
1337- &rate->rate_min,
1338- &rate->rate_max);
1406+ if (!err)
13391407 return 0;
1340- }
13411408
13421409 /* try to open with the old protocol version */
13431410 rate->plugin_version = SND_PCM_RATE_PLUGIN_VERSION_OLD;
13441411 err = open_func(SND_PCM_RATE_PLUGIN_VERSION_OLD,
13451412 &rate->obj, &rate->ops);
1346- if (err) {
1347- snd_dlobj_cache_put(open_func);
1348- rate->open_func = NULL;
1349- }
1413+ if (!err)
1414+ return 0;
1415+
1416+ snd_dlobj_cache_put(open_func);
1417+ rate->open_func = NULL;
13501418 return err;
13511419 }
13521420 #endif
@@ -1417,6 +1485,10 @@ int snd_pcm_rate_open(snd_pcm_t **pcmp, const char *name,
14171485 rate->srate = srate;
14181486 rate->sformat = sformat;
14191487
1488+ rate->rate_min = SND_PCM_PLUGIN_RATE_MIN;
1489+ rate->rate_max = SND_PCM_PLUGIN_RATE_MAX;
1490+ rate->plugin_version = SND_PCM_RATE_PLUGIN_VERSION;
1491+
14201492 err = snd_pcm_new(&pcm, SND_PCM_TYPE_RATE, name, slave->stream, slave->mode);
14211493 if (err < 0) {
14221494 free(rate);
@@ -1496,6 +1568,8 @@ int snd_pcm_rate_open(snd_pcm_t **pcmp, const char *name,
14961568 return err;
14971569 }
14981570
1571+ rate_initial_setup(rate);
1572+
14991573 pcm->ops = &snd_pcm_rate_ops;
15001574 pcm->fast_ops = &snd_pcm_rate_fast_ops;
15011575 pcm->private_data = rate;
Show on old repository browser