这一章将对播放音频的具体内容做讲解。我的想法是按照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操作。后续的几节将围绕这几个内容展开。
通过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;
}
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(¶ms);
param_set_mask(¶ms, SNDRV_PCM_HW_PARAM_FORMAT,
pcm_format_to_alsa(config->format));
param_set_min(¶ms, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, config->period_size);
param_set_int(¶ms, SNDRV_PCM_HW_PARAM_CHANNELS,
config->channels);
param_set_int(¶ms, SNDRV_PCM_HW_PARAM_PERIODS, config->period_count);
param_set_int(¶ms, 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(¶ms, SNDRV_PCM_HW_PARAM_ACCESS,
SNDRV_PCM_ACCESS_MMAP_INTERLEAVED);
else
param_set_mask(¶ms, SNDRV_PCM_HW_PARAM_ACCESS,
SNDRV_PCM_ACCESS_RW_INTERLEAVED);
if (pcm->ops->ioctl(pcm->data, SNDRV_PCM_IOCTL_HW_PARAMS, ¶ms)) {
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(¶ms, SNDRV_PCM_HW_PARAM_PERIOD_SIZE);
pcm->config.period_count = param_get_int(¶ms, 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。
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中对各个成员的介绍。
我的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
有没有办法跳过CSV文件的第一行,让第二行作为标题?我有一个CSV文件,第一行是日期,第二行是标题,所以我需要能够在遍历它时跳过第一行。我尝试使用slice但它会将CSV转换为数组,我真的很想将其读取为CSV,以便我可以利用header。 最佳答案 根据您的数据,您可以使用另一种方法和skip_lines-option此示例跳过所有以#开头的行require'csv'CSV.parse(DATA.read,:col_sep=>';',:headers=>true,:skip_lines=>/^#/#Markcomments!)do|
我的任务是从数组中选择最高和最低的数字。我想我很清楚我想做什么,但只是努力以正确的格式访问信息以满足通过标准。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,
我一直在寻找一种以编程方式或通过命令行将mp3转换为aac的方法,但没有成功。理想情况下,我有一段代码可以从我的Rails应用程序中调用,将mp3转换为aac。我安装了ffmpeg和libfaac,并能够使用以下命令创建aac文件:ffmpeg-itest.mp3-acodeclibfaac-ab163840dest.aac当我将输出文件的名称更改为dest.m4a时,它无法在iTunes中播放。谢谢! 最佳答案 FFmpeg提供AAC编码功能(如果您已编译它们)。如果您使用的是Windows,则可以从here获取完整的二进制文件。
或者好像我必须自己写方法?(保持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)。但我相信我已经为您找到了答
我有这个字符串:auteur="comtedeFlandreetHainaut,Baudouin,Jacques,Thierry"我想删除第一个逗号之前的所有内容,即在这种情况下保留“Baudouin,Jacques,Thierry”试过这个:nom=auteur.gsub(/.*,/,'')但这会删除最后一个逗号之前的每个逗号,只保留“Thierry”。 最佳答案 auteur.partition(",").last#=>"Baudouin,Jacques,Thierry" 关于rub
我有一个以时间戳为键的哈希。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的准确
我有一个字符串数组,我需要从中提取第一个单词,将它们转换为整数并获得它们的总和。示例:["5Apple","5Orange","15Grapes"]预期输出=>25我的尝试:["5","5","15"].map(&:to_i).sum 最佳答案 我从你的问题中找到了答案。["5Apple","5Orange","15Grapes"].map(&:to_i).sum在数组中,如果存在任何整数可转换值,那么它将自动转换为整数。 关于arrays-字符串数组中字符串第一部分的总和,我们在Sta
在我看来,我正在尝试显示一个对象表,这是我的代码:CategoriesCBB's">然而这是抛出一个错误说:can'tconvertCapabilityBuildingBlockintoArray关系是正确的,错误来self尝试在此处减去数组的第一个对象的行:有什么方法可以忽略数组中的第一个对象来遍历数组吗?谢谢 最佳答案 尝试使用Array.drop-http://www.ruby-doc.org/core/classes/Array.html#M000294 关于ruby-on-ra
我正在使用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/