אתה מקליט אודיו באפליקציה ניידת, שולח אותו ל-OpenAI Whisper API — ומקבל שגיאה 413 Request Entity Too Large או SocketTimeoutException עקב מגבלת 25 MB ועיבוד איטי. הפתרון הוא חלוקה לקטעים וכיוונון פרמטרים נכון. המהנדסים שלנו, המוסמכים על ידי Apple ו-Google, עם ניסיון של 5+ שנים ולמעלה מ-50 פרויקטים, חידדו את התהליך עבור iOS (Swift) ו-Android (Kotlin): מחיתוך אודיו ועד תמלול רב-לשוני עם חותמות זמן. Whisper API — Whisper (מערכת זיהוי דיבור) — הוא מודל פתוח של OpenAI הזמין דרך REST API. הדיוק שלו ברוסית מגיע ל-94% (WER ~6%), קרוב לרמה אנושית. עם זאת, אינטגרציה דורשת טיפול במגבלות ספציפיות לפלטפורמה: עבודה עם זרמי אודיו, פורמטים ומשימות רקע היא בסיסית.
עקיפת מגבלת 25 MB
המגבלה של POST /v1/audio/transcriptions היא 25 MB. דקה אחת של MP3 ב-128 kbps היא ~1 MB, כך שקטע יכול להיות עד 25 דקות. להקלטות ארוכות יותר, נדרשת חיתוך. בפרויקט קודם עם הקלטות אודיו של הרצאות, חלוקה לקטעים הפחיתה את זמן התמלול הכולל מ-15 ל-4 דקות.
iOS: AVAssetExportSession עם timeRange. דוגמה:
let exportSession = AVAssetExportSession(asset: asset, presetName: AVAssetExportPresetAppleM4A)
exportSession?.timeRange = CMTimeRange(start: startTime, duration: chunkDuration)Android: let exportSession = AVAssetExportSession(asset: asset, presetName: AVAssetExportPresetAppleM4A) exportSession?.timeRange = CMTimeRange(start: startTime, duration: chunkDuration) + MediaExtractor — חיתוך ללא קידוד מחדש אם ה-codec של המקור תואם (AAC ב-MP4). עבור codec אחרים — המרה דרך MediaCodec ל-PCM → WAV.
Whisper API מחזיר 503 בעומס יתר. Exponential backoff עם 3 ניסיונות חוזרים פותר את הבעיה ב-99% מהמקרים. ב-Android השתמש ב-OkHttp עם interceptor לניסיונות חוזרים, ב-iOS — URLSession עם delegate.
פורמטי אודיו והמרה
הפורמטים האופטימליים הם MP3 ו-M4A (AAC). ודא שה-codec בתוך ה-container נתמך: אחרי MediaMuxer עם preset AVAssetExportSession זה תמיד AAC, ב-Android עדיף להמיר ל-WAV. הפרמטר AppleM4A מחזיר טקסט עם חותמות זמן — חיוני לסנכרון.
אילו פרמטרים משפרים דיוק?
| פרמטר | המלצה | השפעה |
|---|---|---|
| prompt | עד 224 tokens של הקשר (למשל, מונחי תחום) | מפחית WER על מילים מיוחדות |
| temperature | 0 | פלט דטרמיניסטי |
| language | ציין במפורש (למשל, "ru") | מאיץ עיבוד |
השוואה עם חלופות: Whisper API זול פי 3 ממודל ביתי עם איכות דומה, וחוסך עד 40% בעלויות ענן בהשוואה ל-Google Speech-to-Text.
למה חלוקה לקטעים קריטית לתמלול נייד?
ללא חלוקה, לא ניתן לעבד הקלטות ארוכות — הרצאות, ראיונות, הערות קוליות. חיתוך לקטעים של 25 MB עם חפיפה של 1–2 שניות מבטיח שאף מילה לא תאבד בגבולות. אנו משתמשים בשליחה מקבילה של קטעים עם response_format=verbose_json ב-iOS ו-DispatchGroup ב-Android, ומפחיתים את זמן התמלול הכולל ב-30%.
| פלטפורמה | כלי חיתוך | חפיפה |
|---|---|---|
| iOS | AVAssetExportSession עם timeRange | שנייה אחת |
| Android | MediaExtractor + MediaMuxer | 2 שניות (תלוי ב-codec) |
מה כלול באינטגרציה
- תכנון ארכיטקטורת הקלטה ושליחה
- יישום ב-iOS (Swift) ו-Android (Kotlin)
- טיפול בשגיאות, ניסיונות חוזרים, חלוקה לקטעים
- הגדרת שפה, prompt, פורמט verbose_json
- תיעוד וסקירת קוד
- בדיקות עומס
שלבי עבודה
- ניתוח דרישות — קביעת מקרי שימוש, תדירות תמלול, גודל אודיו.
- אב-טיפוס — יישום אינטגרציה בסיסית בפלטפורמה אחת תוך 2–3 ימים.
- אינטגרציה ובדיקות — הוספת חלוקה, ניסיונות חוזרים, טיפול בשגיאות, תמיכה רב-לשונית.
- פריסה וניטור — הגדרת לוגים, התראות על ירידת דיוק, עדכון prompts.
יישום ב-iOS (Swift)
struct WhisperService {
private let apiKey: String
private let session = URLSession.shared
func transcribe(audioURL: URL, language: String = "ru") async throws -> String {
var request = URLRequest(url: URL(string: "https://api.openai.com/v1/audio/transcriptions")!)
request.httpMethod = "POST"
request.setValue("Bearer \(apiKey)", forHTTPHeaderField: "Authorization")
let boundary = UUID().uuidString
request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
var body = Data()
body.append("--\(boundary)\r\n".data(using: .utf8)!)
body.append("Content-Disposition: form-data; name=\"file\"; filename=\"audio.m4a\"\r\n".data(using: .utf8)!)
body.append("Content-Type: audio/m4a\r\n\r\n".data(using: .utf8)!)
body.append(try Data(contentsOf: audioURL))
body.append("\r\n".data(using: .utf8)!)
body.append("--\(boundary)\r\n".data(using: .utf8)!)
body.append("Content-Disposition: form-data; name=\"model\"\r\n\r\nwhisper-1\r\n".data(using: .utf8)!)
body.append("--\(boundary)\r\n".data(using: .utf8)!)
body.append("Content-Disposition: form-data; name=\"language\"\r\n\r\n\(language)\r\n".data(using: .utf8)!)
body.append("--\(boundary)--\r\n".data(using: .utf8)!)
request.httpBody = body
let (data, _) = try await session.data(for: request)
let response = try JSONDecoder().decode(TranscriptionResponse.self, from: data)
return response.text
}
} יישום ב-Android (Kotlin)
suspend fun transcribe(file: File, language: String = "ru"): String {
val client = OkHttpClient.Builder()
.readTimeout(120, TimeUnit.SECONDS)
.build()
val requestBody = MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart("file", file.name, file.asRequestBody("audio/mp4".toMediaType()))
.addFormDataPart("model", "whisper-1")
.addFormDataPart("language", language)
.build()
val request = Request.Builder()
.url("https://api.openai.com/v1/audio/transcriptions")
.header("Authorization", "Bearer $apiKey")
.post(requestBody)
.build()
return withContext(Dispatchers.IO) {
client.newCall(request).execute().use { response ->
val json = response.body!!.string()
JSONObject(json).getString("text")
}
}
} טעויות אינטגרציה נפוצות
טעינת CoroutineScope כולו לזיכרון — בקובץ של 100 MB זה גורם ל-OOM במכשירי Android תקציביים. השתמש ב-struct WhisperService { private let apiKey: String private let session = URLSession.shared func transcribe(audioURL: URL, language: String = "ru") async throws -> String { var request = URLRequest(url: URL(string: "https://api.openai.com/v1/audio/transcriptions")!) request.httpMethod = "POST" request.setValue("Bearer \(apiKey)", forHTTPHeaderField: "Authorization") let boundary = UUID().uuidString request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type") var body = Data() body.append("--\(boundary)\r\n".data(using: .utf8)!) body.append("Content-Disposition: form-data; name=\"file\"; filename=\"audio.m4a\"\r\n".data(using: .utf8)!) body.append("Content-Type: audio/m4a\r\n\r\n".data(using: .utf8)!) body.append(try Data(contentsOf: audioURL)) body.append("\r\n".data(using: .utf8)!) body.append("--\(boundary)\r\n".data(using: .utf8)!) body.append("Content-Disposition: form-data; name=\"model\"\r\n\r\nwhisper-1\r\n".data(using: .utf8)!) body.append("--\(boundary)\r\n".data(using: .utf8)!) body.append("Content-Disposition: form-data; name=\"language\"\r\n\r\n\(language)\r\n".data(using: .utf8)!) body.append("--\(boundary)--\r\n".data(using: .utf8)!) request.httpBody = body let (data, _) = try await session.data(for: request) let response = try JSONDecoder().decode(TranscriptionResponse.self, from: data) return response.text } } ב-OkHttp או העלאה מבוססת InputStream ב-iOS. חוסר לוגיקת ניסיונות חוזרים: Whisper API מחזיר מדי פעם 503 — exponential backoff עם 3 ניסיונות פותר זאת. אחסון מפתח API בצד הלקוח — יש להעביר את המפתח דרך backend.
לוחות זמנים ותהליך
אינטגרציה בסיסית בפלטפורמה אחת — 3–5 ימים. עם חלוקה, verbose_json וניסיונות חוזרים — 8–13 ימים. תמיכה רב-לשונית היא שלב נפרד. צור קשר להערכת הפרויקט שלך — נתכנן ארכיטקטורה אופטימלית תוך יום אחד. קבל ייעוץ על אינטגרציית Whisper API עוד היום.
עיבוד אודיו ברקע
להקלטות ארוכות, חשוב להריץ תמלול ברקע. ב-iOS השתמש ב-suspend fun transcribe(file: File, language: String = "ru"): String { val client = OkHttpClient.Builder() .readTimeout(120, TimeUnit.SECONDS) .build() val requestBody = MultipartBody.Builder() .setType(MultipartBody.FORM) .addFormDataPart("file", file.name, file.asRequestBody("audio/mp4".toMediaType())) .addFormDataPart("model", "whisper-1") .addFormDataPart("language", language) .build() val request = Request.Builder() .url("https://api.openai.com/v1/audio/transcriptions") .header("Authorization", "Bearer $apiKey") .post(requestBody) .build() return withContext(Dispatchers.IO) { client.newCall(request).execute().use { response -> val json = response.body!!.string() JSONObject(json).getString("text") } } } , ב-Android — Data(contentsOf:). זה מאפשר למשתמשים למזער את האפליקציה ללא אובדן נתונים. אנו מבטיחים פעולה יציבה גם באינטרנט חלש — אם בקשה נכשלת, אנו חוזרים אוטומטית עם exponential backoff ומודיעים למשתמש על התקדמות. כדי להתחיל פיילוט, צור קשר — נספק גישת API לבדיקה ונעזור להגדיר את הארכיטקטורה לתרחיש שלך.







