音声(音響)録音は Camtrap DP の bioacoustics
フレーバーで扱います。音声データは
メディアベース(observationLevel = "media")で、各観察はメディアファイル
(mediaID)を参照します。カメラトラップとの差分は音響スキーマから自動的に読み取られ、
主な点は次のとおりです。
camera* の代わりに
device* 列(deviceID,
deviceModel など)+ elevation,
devicePlatform, recordingSchedule,
locationType。duration,
samplingFrequency, bitDepth,
gain, channels
を追加。fileMediatype は audio/wav
等の音声タイプ。frequencyLow,
frequencyHigh を追加。cameraSetupType は
deviceSetupType に。deployments は %Y-%m-%dT%H:%M:%S%z
(小数秒なし)、media / observations
の時刻は小数秒付き %Y-%m-%dT%H:%M:%S.%f%z。project$captureMethod の音声向けの値は
activityDetection / continuous /
recordingSchedule(timeLapse は不可)。ヒント — 日時は
POSIXctで渡す。 そうすれば各テーブルの書式へ正しく整形され (オフセット+0900、必要な箇所には小数秒.000)。"2026/6/12 12:00:00"の ような生文字列はそのまま書き出され検証に失敗します。
この例では、野帳が
設置情報の野帳と観察情報の野帳の2つだけあり、観察野帳に
音声のファイル名が記録されている前提で、そこから
media を作成します。
data("Adep") # 設置情報の野帳(デバイス設置1件=1行)
data("Aobs") # 観察情報の野帳(観察1件=1行。`filename` を持つ)
str(Adep, vec.len = 2)
#> 'data.frame': 2 obs. of 14 variables:
#> $ deploymentID : chr "AC01" "AC02"
#> $ longitude : num 139 139
#> $ latitude : num 34.9 34.9
#> $ locationID : chr "L01" "L02"
#> $ startDate : chr "2026-06-12" "2026-06-13"
#> $ startTime : chr "06:00:00" "06:00:00"
#> $ endDate : chr "2026-06-20" "2026-06-21"
#> $ endTime : chr "06:00:00" "06:00:00"
#> $ deviceID : chr "AM1" "AM2"
#> $ deviceModel : chr "AudioMoth 1.2.0" "AudioMoth 1.2.0"
#> $ samplingFrequency: int 48000 48000
#> $ bitDepth : int 16 16
#> $ channels : int 1 1
#> $ setupBy : chr "Jane Doe" "Jane Doe"
str(Aobs, vec.len = 2)
#> 'data.frame': 6 obs. of 19 variables:
#> $ institutionCode: chr "NIES" "NIES" ...
#> $ collectionCode : chr "ACO" "ACO" ...
#> $ obsID : chr "1" "2" ...
#> $ eventID : chr "e1" "e1" ...
#> $ deploymentID : chr "AC01" "AC01" ...
#> $ locationID : chr "L01" "L01" ...
#> $ date : chr "2026-06-12" "2026-06-12" ...
#> $ time : chr "06:00:00" "06:00:00" ...
#> $ filename : chr "AM1_20260612_060000.wav" "AM1_20260612_060000.wav" ...
#> $ duration : num 60 60 60 60 60 ...
#> $ object : chr "animal" "animal" ...
#> $ class : chr "Aves" "Aves" ...
#> $ genus : chr "Strix" "Cuculus" ...
#> $ species : chr "uralensis" "canorus" ...
#> $ individualCount: int NA NA NA NA NA ...
#> $ frequencyLow : num 300 500 300 NA 500 ...
#> $ frequencyHigh : num 900 900 900 NA 900 ...
#> $ eventStart : chr "2026-06-12 06:00:05" "2026-06-12 06:00:12" ...
#> $ eventEnd : chr "2026-06-12 06:00:09" "2026-06-12 06:00:18" ...Adep は
deploymentID、座標、startDate/startTime、endDate/endTime、
deviceID、deviceModel、録音設定(samplingFrequency,
bitDepth, channels)、setupBy
を持ちます。Aobs は
deploymentID、filename、
date/time、duration、分類(class/genus/species)、individualCount、
frequencyLow/frequencyHigh、eventStart/eventEnd
を持ちます。この例データでは、
座標は伊豆半島内、individualCount は
NA(音響では個体数を数えない)、
frequencyLow/frequencyHigh
は文献に基づく概算値です。
ba <- "https://raw.githubusercontent.com/camera-traps/bioacoustics/main/camtrap-dp/1.0.2/%s"
dp <- R6_CamtrapDP$new(version = "1.0.2",
title = "音響調査の例", description = "AudioMoth 録音",
id = "https://example.org/dataset/acoustic-1")
dp$set_properties(
version = "1.0.2",
profile = sprintf(ba, "camtrap-dp-profile-acoustic.json"),
schema_urls = list(
deployments = sprintf(ba, "deployments-table-schema-acoustic.json"),
media = sprintf(ba, "media-table-schema-acoustic.json"),
observations = sprintf(ba, "observations-table-schema-acoustic.json")))Adep から deployments を作成します。日付列と時刻列を
POSIXct に結合して渡すと、
正しい日時書式で書き出されます。
deployments <- data.frame(
deploymentID = Adep$deploymentID,
latitude = Adep$latitude,
longitude = Adep$longitude,
locationID = Adep$locationID,
deploymentStart = as.POSIXct(paste(Adep$startDate, Adep$startTime), tz = "Asia/Tokyo"),
deploymentEnd = as.POSIXct(paste(Adep$endDate, Adep$endTime), tz = "Asia/Tokyo"),
deviceID = Adep$deviceID,
deviceModel = Adep$deviceModel,
setupBy = Adep$setupBy,
stringsAsFactors = FALSE)メディア用の野帳は無いので、Aobs のユニークな
filename(音声ファイル1件=1行)から media
を作り、録音設定は Adep から結合します。
files <- Aobs[!duplicated(Aobs$filename), ] # 音声ファイルごとに1行
media <- data.frame(
mediaID = files$filename, # ファイル名をメディア識別子に
deploymentID = files$deploymentID,
timestamp = as.POSIXct(paste(files$date, files$time), tz = "Asia/Tokyo"),
filePath = file.path("audio", files$filename),
filePublic = TRUE,
fileMediatype = paste0("audio/", tolower(tools::file_ext(files$filename))), # "audio/wav"
duration = files$duration,
stringsAsFactors = FALSE)
# 録音設定(samplingFrequency / bitDepth / channels)を Adep から付与
media <- merge(media, Adep[, c("deploymentID", "samplingFrequency", "bitDepth", "channels")],
by = "deploymentID", all.x = TRUE)
head(media)
#> deploymentID mediaID timestamp
#> 1 AC01 AM1_20260612_060000.wav 2026-06-12 06:00:00
#> 2 AC01 AM1_20260612_063000.wav 2026-06-12 06:30:00
#> 3 AC01 AM1_20260613_060000.wav 2026-06-13 06:00:00
#> 4 AC02 AM2_20260613_060000.wav 2026-06-13 06:00:00
#> filePath filePublic fileMediatype duration
#> 1 audio/AM1_20260612_060000.wav TRUE audio/wav 60
#> 2 audio/AM1_20260612_063000.wav TRUE audio/wav 60
#> 3 audio/AM1_20260613_060000.wav TRUE audio/wav 60
#> 4 audio/AM2_20260613_060000.wav TRUE audio/wav 60
#> samplingFrequency bitDepth channels
#> 1 48000 16 1
#> 2 48000 16 1
#> 3 48000 16 1
#> 4 48000 16 1timestamp は POSIXct なので、音響
media の書式に合わせて小数秒付き (例
2026-06-12T06:00:00.000+0900)で自動的に書き出されます。
observations <- data.frame(
observationID = paste(Aobs$deploymentID, Aobs$eventID, Aobs$obsID, sep = "_"),
deploymentID = Aobs$deploymentID,
mediaID = Aobs$filename, # media へのリンク(mediaID = filename)
eventStart = as.POSIXct(Aobs$eventStart, tz = "Asia/Tokyo"),
eventEnd = as.POSIXct(Aobs$eventEnd, tz = "Asia/Tokyo"),
observationLevel = "media",
observationType = ifelse(Aobs$object == "none", "blank",
ifelse(Aobs$object == "hito", "human", "animal")),
scientificName = ifelse(is.na(Aobs$genus), Aobs$class, paste(Aobs$genus, Aobs$species)),
count = Aobs$individualCount, # 音響では個体数を数えないため NA
frequencyLow = Aobs$frequencyLow,
frequencyHigh = Aobs$frequencyHigh,
stringsAsFactors = FALSE)dp$add_contributors(data.frame(title = "Jane Doe", role = "contact",
organization = "NIES", stringsAsFactors = FALSE))
dp$add_license(name = "CC0-1.0", scope = "data")
dp$add_license(name = "CC-BY-4.0", scope = "media")
dp$set_project(title = "音響調査", samplingDesign = "systematicRandom",
captureMethod = "recordingSchedule", individualAnimals = FALSE,
observationLevel = "media")
dp$set_st()
# dp$set_taxon() # GBIF/ITIS/NCBI から taxonID。taxadb パッケージ+ネット接続が必要
dp$check_relations() # PK/FK。キー列が欠損なら datapackage$data$... を指す警告を出す
path <- file.path(tempdir(), "acoustic-package")
dp$out_camtrapdp(write = TRUE, directory = path)
issues <- dp$validate_frictionless(directory = path, python = "python") # 要 pip install frictionless
ctdp_is_valid(issues)既にディスク上にあるパッケージを上書きせず検証だけする場合:
ba <- "https://raw.githubusercontent.com/camera-traps/bioacoustics/main/camtrap-dp/1.0.2/%s"
acoustic_media <- TableSchema$new(
"media", version = "1.0.2",
url_template = sprintf(ba, "media-table-schema-acoustic.json"))
acoustic_media$field_names()
acoustic_media$requirements() # 各列の type / format / required / enum
acoustic_media$field("timestamp")$format # "%Y-%m-%dT%H:%M:%S.%f%z"