草庐IT

6.播放音频(第一部分)

junguo 2023-08-16 原文

6.1 从tinyalsa开始

这一章将对播放音频的具体内容做讲解。我的想法是按照tinyalsa中的例子作为讲解的范本,因为tinyalsa足够简单,很多时候都忽略了它的细节。趁着这个机会再整理一下tinyalsa的内容。我使用的tinyalsa从https://github.com/tinyalsa/tinyalsa下载,从examples/writei.c开始。

int main(void)
{
    void *frames;
    size_t size;

    size = read_file(&frames);
    if (size == 0) {
        return EXIT_FAILURE;
    }

    if (write_frames(frames, size) < 0) {
        return EXIT_FAILURE;
    }

    free(frames);
    return EXIT_SUCCESS;
}

其中函数read_file从指定的文件中读取pcm数据到frames。

static int write_frames(const void * frames, size_t byte_count){

    unsigned int card = 0;
    unsigned int device = 0;
    int flags = PCM_OUT;

    const struct pcm_config config = {
        .channels = 2,
        .rate = 48000,
        .format = PCM_FORMAT_S32_LE,
        .period_size = 1024,
        .period_count = 2,
        .start_threshold = 1024,
        .silence_threshold = 1024 * 2,
        .stop_threshold = 1024 * 2
    };

    struct pcm * pcm = pcm_open(card, device, flags, &config);
    if (pcm == NULL) {
        fprintf(stderr, "failed to allocate memory for PCM\n");
        return -1;
    } else if (!pcm_is_ready(pcm)){
        pcm_close(pcm);
        fprintf(stderr, "failed to open PCM\n");
        return -1;
    }

    unsigned int frame_count = pcm_bytes_to_frames(pcm, byte_count);

    int err = pcm_writei(pcm, frames, frame_count);
    if (err < 0) {
      printf("error: %s\n", pcm_get_error(pcm));
    }

    pcm_close(pcm);

    return 0;
}

这个函数里通过pcm_open打开设备,后面通过pcm_writei去写数据。这里先注意一下pcm_config中的内容,包括声道数、采样数、数据格式等内容。period_size表示的内核中DMA块的大小,period_count表示这样的块有几个,period_count的大小应该大于2,这样才能保证音频播放过程中无卡顿。start_threshold用来表示有多少帧数据的时候,才开始播放声音。silence_threshold和stop_threshold表示静音和停止播放时候需要的帧数,帧数太少的时候进行操作,可能导致破音的产生。

struct pcm *pcm_open(unsigned int card, unsigned int device,
                     unsigned int flags, const struct pcm_config *config)
{
    struct pcm *pcm;
    struct snd_pcm_info info;
    int rc;

    pcm = calloc(1, sizeof(struct pcm));
    if (!pcm) {
        oops(&bad_pcm, ENOMEM, "can't allocate PCM object");
        return &bad_pcm;
    }

    /* Default to hw_ops, attemp plugin open only if hw (/dev/snd/pcm*) open fails */
    pcm->ops = &hw_ops;
    pcm->fd = pcm->ops->open(card, device, flags, &pcm->data, NULL);

#ifdef TINYALSA_USES_PLUGINS
    if (pcm->fd < 0) {
        int pcm_type;
        pcm->snd_node = snd_utils_open_pcm(card, device);
        pcm_type = snd_utils_get_node_type(pcm->snd_node);
        if (!pcm->snd_node || pcm_type != SND_NODE_TYPE_PLUGIN) {
            oops(&bad_pcm, ENODEV, "no device (hw/plugin) for card(%u), device(%u)",
                 card, device);
            goto fail_close_dev_node;
        }
        pcm->ops = &plug_ops;
        pcm->fd = pcm->ops->open(card, device, flags, &pcm->data, pcm->snd_node);
    }
#endif
    if (pcm->fd < 0) {
        oops(&bad_pcm, errno, "cannot open device (%u) for card (%u)",
             device, card);
        goto fail_close_dev_node;
    }

    pcm->flags = flags;

    if (pcm->ops->ioctl(pcm->data, SNDRV_PCM_IOCTL_INFO, &info)) {
        oops(&bad_pcm, errno, "cannot get info");
        goto fail_close;
    }
    pcm->subdevice = info.subdevice;

    if (pcm_set_config(pcm, config) != 0)
        goto fail_close;

    rc = pcm_hw_mmap_status(pcm);
    if (rc < 0) {
        oops(&bad_pcm, errno, "mmap status failed");
        goto fail;
    }

#ifdef SNDRV_PCM_IOCTL_TTSTAMP
    if (pcm->flags & PCM_MONOTONIC) {
        int arg = SNDRV_PCM_TSTAMP_TYPE_MONOTONIC;
        rc = pcm->ops->ioctl(pcm->data, SNDRV_PCM_IOCTL_TTSTAMP, &arg);
        if (rc < 0) {
            oops(&bad_pcm, errno, "cannot set timestamp type");
            goto fail;
        }
    }
#endif

    pcm->xruns = 0;
    return pcm;

fail:
    pcm_hw_munmap_status(pcm);
    if (flags & PCM_MMAP)
        pcm->ops->munmap(pcm->data, pcm->mmap_buffer, pcm_frames_to_bytes(pcm, pcm->buffer_size));
fail_close:
    pcm->ops->close(pcm->data);
fail_close_dev_node:
#ifdef TINYALSA_USES_PLUGINS
    if (pcm->snd_node)
        snd_utils_close_dev_node(pcm->snd_node);
#endif
    free(pcm);
    return &bad_pcm;
}

这段代码中关于函数open部分的内容,已经都展开过。其后可以看到通过使用ioctl发送SNDRV_PCM_IOCTL_INFO来获取pcm信息,通过pcm_set_config设置参数以及尝试mmap操作。后续的几节将围绕这几个内容展开。

6.2 获取设置pcm信息

6.2.1 获取信息

通过ioctl发送SNDRV_PCM_IOCTL_INFO就可以得到设置信息。来看一下具体的过程。

之前已经介绍过snd_pcm_f_ops,其中的unlocked_ioctl指向snd_pcm_ioctl,对pcm设备的ioctl操作,都会调用到它。

static long snd_pcm_ioctl(struct file *file, unsigned int cmd,
			  unsigned long arg)
{
	struct snd_pcm_file *pcm_file;

	pcm_file = file->private_data;

// pcm设备的命令由以下的格式命名
// #define SNDRV_PCM_IOCTL_PVERSION	_IOR('A', 0x00, int)
// 第3字节都被定义成了'A',所以这里做个判断

	if (((cmd >> 8) & 0xff) != 'A')
		return -ENOTTY;

	return snd_pcm_common_ioctl(file, pcm_file->substream, cmd,
				     (void __user *)arg);
}

打开文件的过程中,snd_pcm_file被设置到了file的private_data,而大多数操作要处理的是snd_pcm_substream对象,所以pcm_file->substream被传入下一个函数。

而snd_pcm_common_ioctl中基本是一批case语句,选择要处理的具体命令:

case SNDRV_PCM_IOCTL_INFO:
	return snd_pcm_info_user(substream, arg);

获取pcm信息,需要用到snd_pcm_info_user。

int snd_pcm_info_user(struct snd_pcm_substream *substream,
		      struct snd_pcm_info __user * _info)
{
	struct snd_pcm_info *info;
	int err;

	info = kmalloc(sizeof(*info), GFP_KERNEL);
	if (! info)
		return -ENOMEM;
	err = snd_pcm_info(substream, info);
	if (err >= 0) {
		if (copy_to_user(_info, info, sizeof(*info)))
			err = -EFAULT;
	}
	kfree(info);
	return err;
}

通过snd_pcm_info获取信息,然后通过copy_to_user拷贝到用户空间。

int snd_pcm_info(struct snd_pcm_substream *substream, struct snd_pcm_info *info)
{
	struct snd_pcm *pcm = substream->pcm;
	struct snd_pcm_str *pstr = substream->pstr;

	memset(info, 0, sizeof(*info));
	info->card = pcm->card->number;
	info->device = pcm->device;
	info->stream = substream->stream;
	info->subdevice = substream->number;
	strscpy(info->id, pcm->id, sizeof(info->id));
	strscpy(info->name, pcm->name, sizeof(info->name));
	info->dev_class = pcm->dev_class;
	info->dev_subclass = pcm->dev_subclass;
	info->subdevices_count = pstr->substream_count;
	info->subdevices_avail = pstr->substream_count - pstr->substream_opened;
	strscpy(info->subname, substream->name, sizeof(info->subname));

	return 0;
}

6.2.2 设置信息

6.2.2.1 tinyalsa中的实现

int pcm_set_config(struct pcm *pcm, const struct pcm_config *config)
{
    if (pcm == NULL)
        return -EFAULT;
    else if (config == NULL) {
        config = &pcm->config;
        pcm->config.channels = 2;
        pcm->config.rate = 48000;
        pcm->config.period_size = 1024;
        pcm->config.period_count = 4;
        pcm->config.format = PCM_FORMAT_S16_LE;
        pcm->config.start_threshold = config->period_count * config->period_size;
        pcm->config.stop_threshold = config->period_count * config->period_size;
        pcm->config.silence_threshold = 0;
        pcm->config.silence_size = 0;
    } else
        pcm->config = *config;

    //设置硬件参数
    struct snd_pcm_hw_params params;
    param_init(&params);
    param_set_mask(&params, SNDRV_PCM_HW_PARAM_FORMAT,
                   pcm_format_to_alsa(config->format));
    param_set_min(&params, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, config->period_size);
    param_set_int(&params, SNDRV_PCM_HW_PARAM_CHANNELS,
                  config->channels);
    param_set_int(&params, SNDRV_PCM_HW_PARAM_PERIODS, config->period_count);
    param_set_int(&params, SNDRV_PCM_HW_PARAM_RATE, config->rate);

// 非中断模式的处理
    if (pcm->flags & PCM_NOIRQ) {

        if (!(pcm->flags & PCM_MMAP)) {
            oops(pcm, EINVAL, "noirq only currently supported with mmap().");
            return -EINVAL;
        }

        params.flags |= SNDRV_PCM_HW_PARAMS_NO_PERIOD_WAKEUP;
        pcm->noirq_frames_per_msec = config->rate / 1000;
    }

// 是否使用mmap
    if (pcm->flags & PCM_MMAP)
        param_set_mask(&params, SNDRV_PCM_HW_PARAM_ACCESS,
                   SNDRV_PCM_ACCESS_MMAP_INTERLEAVED);
    else
        param_set_mask(&params, SNDRV_PCM_HW_PARAM_ACCESS,
                   SNDRV_PCM_ACCESS_RW_INTERLEAVED);

    if (pcm->ops->ioctl(pcm->data, SNDRV_PCM_IOCTL_HW_PARAMS, &params)) {
        int errno_copy = errno;
        oops(pcm, errno, "cannot set hw params");
        return -errno_copy;
    }

    /* get our refined hw_params */
    pcm->config.period_size = param_get_int(&params, SNDRV_PCM_HW_PARAM_PERIOD_SIZE);
    pcm->config.period_count = param_get_int(&params, SNDRV_PCM_HW_PARAM_PERIODS);
    pcm->buffer_size = config->period_count * config->period_size;

// 处理mmap
    if (pcm->flags & PCM_MMAP) {
        pcm->mmap_buffer = pcm->ops->mmap(pcm->data, NULL, pcm_frames_to_bytes(pcm, pcm->buffer_size),
                                PROT_READ | PROT_WRITE, MAP_SHARED, 0);
        if (pcm->mmap_buffer == MAP_FAILED) {
            int errno_copy = errno;
            oops(pcm, errno, "failed to mmap buffer %d bytes\n",
                 pcm_frames_to_bytes(pcm, pcm->buffer_size));
            return -errno_copy;
        }
    }

// 处理软件参数
    struct snd_pcm_sw_params sparams;
    memset(&sparams, 0, sizeof(sparams));
    sparams.tstamp_mode = SNDRV_PCM_TSTAMP_ENABLE;
    sparams.period_step = 1;
    sparams.avail_min = config->period_size;

    if (!config->start_threshold) {
        if (pcm->flags & PCM_IN)
            pcm->config.start_threshold = sparams.start_threshold = 1;
        else
            pcm->config.start_threshold = sparams.start_threshold =
                config->period_count * config->period_size / 2;
    } else
        sparams.start_threshold = config->start_threshold;

    /* pick a high stop threshold - todo: does this need further tuning */
    if (!config->stop_threshold) {
        if (pcm->flags & PCM_IN)
            pcm->config.stop_threshold = sparams.stop_threshold =
                config->period_count * config->period_size * 10;
        else
            pcm->config.stop_threshold = sparams.stop_threshold =
                config->period_count * config->period_size;
    }
    else
        sparams.stop_threshold = config->stop_threshold;

    sparams.xfer_align = config->period_size / 2; /* needed for old kernels */
    sparams.silence_size = config->silence_size;
    sparams.silence_threshold = config->silence_threshold;
    pcm->boundary = sparams.boundary = pcm->buffer_size;

    while (pcm->boundary * 2 <= INT_MAX - pcm->buffer_size)
        pcm->boundary *= 2;

    if (pcm->ops->ioctl(pcm->data, SNDRV_PCM_IOCTL_SW_PARAMS, &sparams)) {
        int errno_copy = errno;
        oops(pcm, errno, "cannot set sw params");
        return -errno_copy;
    }

    return 0;
}

pcm_config的内容,前面介绍过了。

snd_pcm_hw_params前面看到过,但没有展开,这里看一下:

struct snd_pcm_hw_params {
	unsigned int flags;
	struct snd_mask masks[SNDRV_PCM_HW_PARAM_LAST_MASK -
			       SNDRV_PCM_HW_PARAM_FIRST_MASK + 1];
	struct snd_mask mres[5];	/* reserved masks */
	struct snd_interval intervals[SNDRV_PCM_HW_PARAM_LAST_INTERVAL -
				        SNDRV_PCM_HW_PARAM_FIRST_INTERVAL + 1];
	struct snd_interval ires[9];	/* reserved intervals */
	unsigned int rmask;		/* W: requested masks */
	unsigned int cmask;		/* R: changed masks */
	unsigned int info;		/* R: Info flags for returned setup */
	unsigned int msbits;		/* R: used most significant bits */
	unsigned int rate_num;		/* R: rate numerator */
	unsigned int rate_den;		/* R: rate denominator */
	snd_pcm_uframes_t fifo_size;	/* R: chip FIFO size in frames */
	unsigned char reserved[64];	/* reserved for future */
};

这里的snd_mask与snd_interval在介绍runtime的时候介绍过了。

flags目前看到用到的只有SNDRV_PCM_HW_PARAMS_NO_PERIOD_WAKEUP。

6.2.2.2 SNDRV_PCM_IOCTL_HW_PARAMS

SNDRV_PCM_IOCTL_HW_PARAMS命令用来设置硬件参数,这一节的目的是看看它的具体过程。

前面的过程与ioctl的其它操作类似,在snd_pcm_common_ioctl中,有以下分支:

case SNDRV_PCM_IOCTL_HW_PARAMS:
	return snd_pcm_hw_params_user(substream, arg);

再来看看snd_pcm_hw_params_user的实现:

static int snd_pcm_hw_params_user(struct snd_pcm_substream *substream,
				  struct snd_pcm_hw_params __user * _params)
{
	struct snd_pcm_hw_params *params;
	int err;
// 从用户空间拷贝到内核空间
	params = memdup_user(_params, sizeof(*params));
	if (IS_ERR(params))
		return PTR_ERR(params);

	err = snd_pcm_hw_params(substream, params);
	if (err < 0)
		goto end;
// 返回参数再拷贝回用户空间
	if (copy_to_user(_params, params, sizeof(*params)))
		err = -EFAULT;
end:
	kfree(params);
	return err;
}
还需要继续看snd_pcm_hw_params函数:
static int snd_pcm_hw_params(struct snd_pcm_substream *substream,
			     struct snd_pcm_hw_params *params)
{
	struct snd_pcm_runtime *runtime;
	int err = 0, usecs;
	unsigned int bits;
	snd_pcm_uframes_t frames;

	if (PCM_RUNTIME_CHECK(substream))
		return -ENXIO;
	runtime = substream->runtime;
	mutex_lock(&runtime->buffer_mutex);
	snd_pcm_stream_lock_irq(substream);
	switch (runtime->status->state) {
	case SNDRV_PCM_STATE_OPEN:
	case SNDRV_PCM_STATE_SETUP:
	case SNDRV_PCM_STATE_PREPARED:
		if (!is_oss_stream(substream) &&
		    atomic_read(&substream->mmap_count))
			err = -EBADFD;
		break;
	default:
		err = -EBADFD;
		break;
	}
	snd_pcm_stream_unlock_irq(substream);
	if (err)
		goto unlock;

// 等待sync_irq结束,这里调用不太理解为什么
	snd_pcm_sync_stop(substream, true);

	params->rmask = ~0U;
    // 设置runtime中的snd_pcm_hw_constraints对象
	err = snd_pcm_hw_refine(substream, params);
	if (err < 0)
		goto _error;
// 检查并设置params中参数的值
	err = snd_pcm_hw_params_choose(substream, params);
	if (err < 0)
		goto _error;
// 帮一些没有赋值的参数赋值
	err = fixup_unreferenced_params(substream, params);
	if (err < 0)
		goto _error;
// 分配dma内存
	if (substream->managed_buffer_alloc) {
		err = snd_pcm_lib_malloc_pages(substream,
					       params_buffer_bytes(params));
		if (err < 0)
			goto _error;
		runtime->buffer_changed = err > 0;
	}
// 调用驱动层函数
	if (substream->ops->hw_params != NULL) {
		err = substream->ops->hw_params(substream, params);
		if (err < 0)
			goto _error;
	}
// 给runtime中的各个参数赋值
	runtime->access = params_access(params);
	runtime->format = params_format(params);
	runtime->subformat = params_subformat(params);
	runtime->channels = params_channels(params);
	runtime->rate = params_rate(params);
	runtime->period_size = params_period_size(params);
	runtime->periods = params_periods(params);
	runtime->buffer_size = params_buffer_size(params);
	runtime->info = params->info;
	runtime->rate_num = params->rate_num;
	runtime->rate_den = params->rate_den;
	runtime->no_period_wakeup =
			(params->info & SNDRV_PCM_INFO_NO_PERIOD_WAKEUP) &&
			(params->flags & SNDRV_PCM_HW_PARAMS_NO_PERIOD_WAKEUP);

	bits = snd_pcm_format_physical_width(runtime->format);
	runtime->sample_bits = bits;
	bits *= runtime->channels;
	runtime->frame_bits = bits;
	frames = 1;
	while (bits % 8 != 0) {
		bits *= 2;
		frames *= 2;
	}
	runtime->byte_align = bits / 8;
	runtime->min_align = frames;

	/* Default sw params */
	runtime->tstamp_mode = SNDRV_PCM_TSTAMP_NONE;
	runtime->period_step = 1;
	runtime->control->avail_min = runtime->period_size;
	runtime->start_threshold = 1;
	runtime->stop_threshold = runtime->buffer_size;
	runtime->silence_threshold = 0;
	runtime->silence_size = 0;
	runtime->boundary = runtime->buffer_size;
	while (runtime->boundary * 2 <= LONG_MAX - runtime->buffer_size)
		runtime->boundary *= 2;

	/* clear the buffer for avoiding possible kernel info leaks */
	if (runtime->dma_area && !substream->ops->copy_user) {
		size_t size = runtime->dma_bytes;

		if (runtime->info & SNDRV_PCM_INFO_MMAP)
			size = PAGE_ALIGN(size);
		memset(runtime->dma_area, 0, size);
	}

	snd_pcm_timer_resolution_change(substream);
	snd_pcm_set_state(substream, SNDRV_PCM_STATE_SETUP);

	if (cpu_latency_qos_request_active(&substream->latency_pm_qos_req))
		cpu_latency_qos_remove_request(&substream->latency_pm_qos_req);
	usecs = period_to_usecs(runtime);
	if (usecs >= 0)
		cpu_latency_qos_add_request(&substream->latency_pm_qos_req,
					    usecs);
	err = 0;
 _error:
	if (err) {
		/* hardware might be unusable from this time,
		 * so we force application to retry to set
		 * the correct hardware parameter settings
		 */
		snd_pcm_set_state(substream, SNDRV_PCM_STATE_OPEN);
		if (substream->ops->hw_free != NULL)
			substream->ops->hw_free(substream);
		if (substream->managed_buffer_alloc)
			snd_pcm_lib_free_pages(substream);
	}
 unlock:
	mutex_unlock(&runtime->buffer_mutex);
	return err;
}

函数有点长,依次来看。

snd_pcm_hw_refine ,在runtime中讲过了,根据规则来设置snd_pcm_hw_constraints对象。

snd_pcm_hw_params_choose用来检查并设置用户空间传入的参数,它会去设置以下参数的值:

static const int vars[] = {
	SNDRV_PCM_HW_PARAM_ACCESS,
	SNDRV_PCM_HW_PARAM_FORMAT,
	SNDRV_PCM_HW_PARAM_SUBFORMAT,
	SNDRV_PCM_HW_PARAM_CHANNELS,
	SNDRV_PCM_HW_PARAM_RATE,
	SNDRV_PCM_HW_PARAM_PERIOD_TIME,
	SNDRV_PCM_HW_PARAM_BUFFER_SIZE,
	SNDRV_PCM_HW_PARAM_TICK_TIME,
	-1
};

原则是除了SNDRV_PCM_HW_PARAM_BUFFER_SIZE设置为参数里的最大值外,其它的都会设置为最小值。有这么一个操作的原因是用户空间传入的值可能为多个,不过一般程序都不会这么做,比如在tinyalsa中,传入的参数一般是:

static void param_set_int(struct snd_pcm_hw_params *p, int n, unsigned int val)
{
    if (param_is_interval(n)) {
        struct snd_interval *i = param_to_interval(p, n);
        i->min = val;
        i->max = val;
        i->integer = 1;
    }
}

以单个整数的形式传入。

fixup_unreferenced_params帮一些没有赋值的参数赋默认值。

substream->managed_buffer_alloc用来判断是否由框架代码代为管理dma buffer。HDA选择是,在snd_hda_attach_pcm_stream中函数中,调用了snd_pcm_set_managed_buffer_all,通过它,managed_buffer_alloc已经被设置成了1。DMA数据的处理将放到一起统一讲解。

然后调用 substream->ops->hw_params,hda中对应的函数是:

static int azx_pcm_hw_params(struct snd_pcm_substream *substream,
			     struct snd_pcm_hw_params *hw_params)
{
	struct azx_pcm *apcm = snd_pcm_substream_chip(substream);
	struct azx *chip = apcm->chip;
	struct azx_dev *azx_dev = get_azx_dev(substream);
	int ret = 0;

	trace_azx_pcm_hw_params(chip, azx_dev);
	dsp_lock(azx_dev);
	if (dsp_is_locked(azx_dev)) {
		ret = -EBUSY;
		goto unlock;
	}

	azx_dev->core.bufsize = 0;
	azx_dev->core.period_bytes = 0;
	azx_dev->core.format_val = 0;

unlock:
	dsp_unlock(azx_dev);
	return ret;
}

这时候其实并没有做什么设置性的处理,简单的帮几个值清零。

snd_pcm_hw_params中其它的代码不需要太多的解释了,可以参看runtime中对各个成员的介绍。

有关6.播放音频(第一部分)的更多相关文章

  1. ruby-on-rails - CarrierWave - PDF - 只选择第一页 - 2

    我的Rails应用程序中安装了carrierwave。但是,当用户上传多页pdf时,我只希望应用程序获取文档中的第一页并将其转换为jpeg。这可能吗?用什么命令?这是我的uploader。#encoding:utf-8classImageUploader[200,300]##defscale(width,height)##dosomething#end#Createdifferentversionsofyouruploadedfiles:version:thumbdoprocess:resize_to_fill=>[150,210]process:convert=>:jpgdefful

  2. ruby - 如何跳过 CSV 文件的第一行并将第二行作为标题 - 2

    有没有办法跳过CSV文件的第一行,让第二行作为标题?我有一个CSV文件,第一行是日期,第二行是标题,所以我需要能够在遍历它时跳过第一行。我尝试使用slice但它会将CSV转换为数组,我真的很想将其读取为CSV,以便我可以利用header。 最佳答案 根据您的数据,您可以使用另一种方法和skip_lines-option此示例跳过所有以#开头的行require'csv'CSV.parse(DATA.read,:col_sep=>';',:headers=>true,:skip_lines=>/^#/#Markcomments!)do|

  3. arrays - 在一行中选择数组的第一个和最后一个元素 - 2

    我的任务是从数组中选择最高和最低的数字。我想我很清楚我想做什么,但只是努力以正确的格式访问信息以满足通过标准。defhigh_and_low(numbers)array=numbers.split("").map!{|x|x.to_i}array.sort!{|a,b|ba}putsarray[0,-1]end数字可能看起来像"80917234100",要通过,我需要输出"9234"。我正在尝试putsarray.first.last,但一直无法弄明白。 最佳答案 有Array#minmax完全满足您需要的方法:array=[80,

  4. ruby - 如何以编程方式将 mp3 转换为 itunes 可播放的 aac/m4a 文件? - 2

    我一直在寻找一种以编程方式或通过命令行将mp3转换为aac的方法,但没有成功。理想情况下,我有一段代码可以从我的Rails应用程序中调用,将mp3转换为aac。我安装了ffmpeg和libfaac,并能够使用以下命令创建aac文件:ffmpeg-itest.mp3-acodeclibfaac-ab163840dest.aac当我将输出文件的名称更改为dest.m4a时,它无法在iTunes中播放。谢谢! 最佳答案 FFmpeg提供AAC编码功能(如果您已编译它们)。如果您使用的是Windows,则可以从here获取完整的二进制文件。

  5. ruby-on-rails - Ruby 或 Rails 有只将第一个字符大写的方法吗? - 2

    或者好像我必须自己写方法?(保持DHA不变):ruby-1.9.2-p180:001>s='omega-3(DHA)'=>"omega-3(DHA)"ruby-1.9.2-p180:002>s.capitalize=>"Omega-3(dha)"ruby-1.9.2-p180:003>s.titleize=>"Omega3(Dha)"ruby-1.9.2-p180:005>s[0].upcase+s[1..-1]=>"Omega-3(DHA)" 最佳答案 如果我的回答只是垃圾,我深表歉意(我不做ruby)。但我相信我已经为您找到了答

  6. ruby - gsub 删除第一个逗号前的所有内容 - 2

    我有这个字符串:auteur="comtedeFlandreetHainaut,Baudouin,Jacques,Thierry"我想删除第一个逗号之前的所有内容,即在这种情况下保留“Baudouin,Jacques,Thierry”试过这个:nom=auteur.gsub(/.*,/,'')但这会删除最后一个逗号之前的每个逗号,只保留“Thierry”。 最佳答案 auteur.partition(",").last#=>"Baudouin,Jacques,Thierry" 关于rub

  7. ruby-on-rails - Order Hash 并删除第一个键值对 - 2

    我有一个以时间戳为键的哈希。hash={"2016-05-31T22:30:58+02:00"=>{"path"=>"/","method"=>"GET"},"2016-05-31T22:31:23+02:00"=>{"path"=>"/tour","method"=>"GET"},"2016-05-31T22:31:05+02:00"=>{"path"=>"/contact_us","method"=>"GET"}}我订购了这个系列并得到了第一双这样的:hash.sort_by{|k,_|k}.first.first但是我该如何删除它呢?删除方法requiresyou知道key的准确

  8. arrays - 字符串数组中字符串第一部分的总和 - 2

    我有一个字符串数组,我需要从中提取第一个单词,将它们转换为整数并获得它们的总和。示例:["5Apple","5Orange","15Grapes"]预期输出=>25我的尝试:["5","5","15"].map(&:to_i).sum 最佳答案 我从你的问题中找到了答案。["5Apple","5Orange","15Grapes"].map(&:to_i).sum在数组中,如果存在任何整数可转换值,那么它将自动转换为整数。 关于arrays-字符串数组中字符串第一部分的总和,我们在Sta

  9. ruby-on-rails - Rails 3 : Looping through array of objects, 忽略数组中的第一个对象? - 2

    在我看来,我正在尝试显示一个对象表,这是我的代码:CategoriesCBB's">然而这是抛出一个错误说:can'tconvertCapabilityBuildingBlockintoArray关系是正确的,错误来self尝试在此处减去数组的第一个对象的行:有什么方法可以忽略数组中的第一个对象来遍历数组吗?谢谢 最佳答案 尝试使用Array.drop-http://www.ruby-doc.org/core/classes/Array.html#M000294 关于ruby-on-ra

  10. ruby-on-rails - 我如何跳过前三行而不是 FasterCSV 中的第一行 - 2

    我正在使用FasterCSV我正在循环使用这样的foreachFasterCSV.foreach("#{Rails.public_path}/uploads/transfer.csv",:encoding=>'u',:headers=>:first_row)do|row|但问题是我的csv将前3行作为标题...有什么方法可以使fasterCSV跳过前三行而不是仅跳过第一行?? 最佳答案 不确定FasterCSV,但在Ruby1.9标准CSV库(由FasterCSV制作)中,我可以执行以下操作:c=CSV.open'/path/to/

随机推荐